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