8a54fe653e96bebfeaf288a006a50e26bf531d0d
[mdsal.git] / binding / mdsal-binding-runtime-api / src / main / java / org / opendaylight / mdsal / binding / runtime / api / BindingRuntimeTypes.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o.  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.binding.runtime.api;
9
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.annotations.Beta;
15 import com.google.common.base.MoreObjects;
16 import com.google.common.collect.BiMap;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMultimap;
19 import com.google.common.collect.Multimap;
20 import com.google.common.collect.MultimapBuilder;
21 import java.util.Collection;
22 import java.util.IdentityHashMap;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
31 import org.opendaylight.mdsal.binding.model.api.Type;
32 import org.opendaylight.yangtools.concepts.Immutable;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
35 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
36 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
37 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
38 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
39 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * The result of BindingGenerator run. Contains mapping between Types and SchemaNodes.
45  */
46 @Beta
47 public final class BindingRuntimeTypes implements EffectiveModelContextProvider, Immutable {
48     private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeTypes.class);
49
50     private final @NonNull EffectiveModelContext schemaContext;
51     private final ImmutableMap<Type, AugmentationSchemaNode> typeToAugmentation;
52     private final ImmutableMap<Type, WithStatus> typeToSchema;
53     private final ImmutableMultimap<Type, Type> choiceToCases;
54     private final ImmutableMap<QName, Type> identities;
55     // Not Immutable as we use two different implementations
56     private final Map<WithStatus, Type> schemaToType;
57
58     public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
59             final Map<Type, AugmentationSchemaNode> typeToAugmentation,
60             final Map<Type, WithStatus> typeToSchema, final Map<WithStatus, Type> schemaToType,
61             final Map<QName, Type> identities) {
62         this.schemaContext = requireNonNull(schemaContext);
63         this.typeToAugmentation = ImmutableMap.copyOf(typeToAugmentation);
64         this.typeToSchema = ImmutableMap.copyOf(typeToSchema);
65         this.identities = ImmutableMap.copyOf(identities);
66
67         // Careful to use identity for SchemaNodes, but only if needed
68         // FIXME: 8.0.0: YT should be switching to identity for equals(), so this should become unnecessary
69         Map<WithStatus, Type> copy;
70         try {
71             copy = ImmutableMap.copyOf(schemaToType);
72         } catch (IllegalArgumentException e) {
73             LOG.debug("Equality-duplicates found in {}", schemaToType.keySet());
74             copy = new IdentityHashMap<>(schemaToType);
75         }
76
77         this.schemaToType = copy;
78
79         // Two-phase indexing of choice/case nodes. First we load all choices. Note we are using typeToSchema argument,
80         // not field, so as not to instantiate its entrySet.
81         final Set<GeneratedType> choiceTypes = typeToSchema.entrySet().stream()
82             .filter(entry -> entry.getValue() instanceof ChoiceEffectiveStatement)
83             .map(entry -> {
84                 final Type key = entry.getKey();
85                 verify(key instanceof GeneratedType, "Unexpected choice type %s", key);
86                 return (GeneratedType) key;
87             })
88             .collect(Collectors.toUnmodifiableSet());
89
90         final Multimap<Type, Type> builder = MultimapBuilder.hashKeys(choiceTypes.size()).arrayListValues().build();
91         for (Entry<Type, WithStatus> entry : typeToSchema.entrySet()) {
92             if (entry.getValue() instanceof CaseEffectiveStatement) {
93                 final Type type = entry.getKey();
94                 verify(type instanceof GeneratedType, "Unexpected case type %s", type);
95                 builder.put(verifyNotNull(implementedChoiceType(((GeneratedType) type).getImplements(), choiceTypes),
96                     "Cannot determine choice type for %s", type), type);
97             }
98         }
99
100         choiceToCases = ImmutableMultimap.copyOf(builder);
101     }
102
103     private static GeneratedType implementedChoiceType(final List<Type> impls, final Set<GeneratedType> choiceTypes) {
104         for (Type impl : impls) {
105             if (impl instanceof GeneratedType && choiceTypes.contains(impl)) {
106                 return (GeneratedType) impl;
107             }
108         }
109         return null;
110     }
111
112     public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
113             final Map<Type, AugmentationSchemaNode> typeToAugmentation,
114             final BiMap<Type, WithStatus> typeToDefiningSchema, final Map<QName, Type> identities) {
115         this(schemaContext, typeToAugmentation, typeToDefiningSchema, typeToDefiningSchema.inverse(), identities);
116     }
117
118     @Override
119     public EffectiveModelContext getEffectiveModelContext() {
120         return schemaContext;
121     }
122
123     public Optional<AugmentationSchemaNode> findAugmentation(final Type type) {
124         return Optional.ofNullable(typeToAugmentation.get(type));
125     }
126
127     public Optional<Type> findIdentity(final QName qname) {
128         return Optional.ofNullable(identities.get(qname));
129     }
130
131     public Optional<WithStatus> findSchema(final Type type) {
132         return Optional.ofNullable(typeToSchema.get(type));
133     }
134
135     public Optional<Type> findType(final WithStatus schema) {
136         return Optional.ofNullable(schemaToType.get(schema));
137     }
138
139     public Optional<Type> findOriginalAugmentationType(final AugmentationSchemaNode augment) {
140         // If the augment statement does not contain any child nodes, we did not generate an augmentation, as it would
141         // be plain littering.
142         // FIXME: MDSAL-695: this check is rather costly (involves filtering), can we just rely on the not being found
143         //                   in the end? all we are saving is essentially two map lookups after all...
144         if (augment.getChildNodes().isEmpty()) {
145             return Optional.empty();
146         }
147
148         // FIXME: MDSAL-695: We should have enough information from mdsal-binding-generator to receive a (sparse) Map
149         //                   for current -> original lookup. When combined with schemaToType, this amounts to the
150         //                   inverse view of what 'typeToSchema' holds
151         AugmentationSchemaNode current = augment;
152         while (true) {
153             // If this augmentation has been added through 'uses foo { augment bar { ... } }', we need to invert that
154             // walk and arrive at the original declaration site, as that is where we generated 'grouping foo's
155             // augmentation. That site may have a different module, hence the augment namespace may be different.
156             final Optional<AugmentationSchemaNode> original = current.getOriginalDefinition();
157             if (original.isEmpty()) {
158                 return findType(current);
159             }
160             current = original.orElseThrow();
161         }
162     }
163
164     public Multimap<Type, Type> getChoiceToCases() {
165         return choiceToCases;
166     }
167
168     public Collection<Type> findCases(final Type choiceType) {
169         return choiceToCases.get(choiceType);
170     }
171
172     @Override
173     public String toString() {
174         return MoreObjects.toStringHelper(this)
175                 .add("typeToAugmentation", typeToAugmentation)
176                 .add("typeToSchema", typeToSchema)
177                 .add("choiceToCases", choiceToCases)
178                 .add("identities", identities)
179                 .toString();
180     }
181 }