2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.runtime.api;
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;
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.ImmutableList;
18 import com.google.common.collect.ImmutableMap;
19 import com.google.common.collect.ImmutableMultimap;
20 import com.google.common.collect.Multimap;
21 import com.google.common.collect.MultimapBuilder;
22 import java.lang.invoke.MethodHandles;
23 import java.lang.invoke.VarHandle;
24 import java.util.Collection;
25 import java.util.IdentityHashMap;
26 import java.util.List;
28 import java.util.Map.Entry;
29 import java.util.Optional;
31 import java.util.stream.Collectors;
32 import org.eclipse.jdt.annotation.NonNull;
33 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
34 import org.opendaylight.mdsal.binding.model.api.Type;
35 import org.opendaylight.yangtools.concepts.Immutable;
36 import org.opendaylight.yangtools.yang.common.QName;
37 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
39 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
40 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
41 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.stmt.CaseEffectiveStatement;
43 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceEffectiveStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The result of BindingGenerator run. Contains mapping between Types and SchemaNodes.
52 public final class BindingRuntimeTypes implements EffectiveModelContextProvider, Immutable {
53 private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeTypes.class);
54 private static final VarHandle TYPE_TO_IDENTIFIER;
58 TYPE_TO_IDENTIFIER = MethodHandles.lookup().findVarHandle(BindingRuntimeTypes.class, "typeToIdentifier",
60 } catch (NoSuchFieldException | IllegalAccessException e) {
61 throw new ExceptionInInitializerError(e);
65 private final @NonNull EffectiveModelContext schemaContext;
66 private final ImmutableMap<Type, AugmentationSchemaNode> typeToAugmentation;
67 private final ImmutableMap<Type, WithStatus> typeToSchema;
68 private final ImmutableMultimap<Type, Type> choiceToCases;
69 private final ImmutableMap<QName, Type> identities;
70 // Not Immutable as we use two different implementations
71 private final Map<WithStatus, Type> schemaToType;
73 @SuppressWarnings("unused")
74 // Accessed via TYPE_TO_IDENTIFIER
75 private volatile ImmutableMap<Type, Absolute> typeToIdentifier = ImmutableMap.of();
77 public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
78 final Map<Type, AugmentationSchemaNode> typeToAugmentation,
79 final Map<Type, WithStatus> typeToSchema, final Map<WithStatus, Type> schemaToType,
80 final Map<QName, Type> identities) {
81 this.schemaContext = requireNonNull(schemaContext);
82 this.typeToAugmentation = ImmutableMap.copyOf(typeToAugmentation);
83 this.typeToSchema = ImmutableMap.copyOf(typeToSchema);
84 this.identities = ImmutableMap.copyOf(identities);
86 // Careful to use identity for SchemaNodes, but only if needed
87 // FIXME: 8.0.0: YT should be switching to identity for equals(), so this should become unnecessary
88 Map<WithStatus, Type> copy;
90 copy = ImmutableMap.copyOf(schemaToType);
91 } catch (IllegalArgumentException e) {
92 LOG.debug("Equality-duplicates found in {}", schemaToType.keySet());
93 copy = new IdentityHashMap<>(schemaToType);
96 this.schemaToType = copy;
98 // Two-phase indexing of choice/case nodes. First we load all choices. Note we are using typeToSchema argument,
99 // not field, so as not to instantiate its entrySet.
100 final Set<GeneratedType> choiceTypes = typeToSchema.entrySet().stream()
101 .filter(entry -> entry.getValue() instanceof ChoiceEffectiveStatement)
103 final Type key = entry.getKey();
104 verify(key instanceof GeneratedType, "Unexpected choice type %s", key);
105 return (GeneratedType) key;
107 .collect(Collectors.toUnmodifiableSet());
109 final Multimap<Type, Type> builder = MultimapBuilder.hashKeys(choiceTypes.size()).arrayListValues().build();
110 for (Entry<Type, WithStatus> entry : typeToSchema.entrySet()) {
111 if (entry.getValue() instanceof CaseEffectiveStatement) {
112 final Type type = entry.getKey();
113 verify(type instanceof GeneratedType, "Unexpected case type %s", type);
114 builder.put(verifyNotNull(implementedChoiceType(((GeneratedType) type).getImplements(), choiceTypes),
115 "Cannot determine choice type for %s", type), type);
119 choiceToCases = ImmutableMultimap.copyOf(builder);
122 private static GeneratedType implementedChoiceType(final List<Type> impls, final Set<GeneratedType> choiceTypes) {
123 for (Type impl : impls) {
124 if (impl instanceof GeneratedType && choiceTypes.contains(impl)) {
125 return (GeneratedType) impl;
131 public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
132 final Map<Type, AugmentationSchemaNode> typeToAugmentation,
133 final BiMap<Type, WithStatus> typeToDefiningSchema, final Map<QName, Type> identities) {
134 this(schemaContext, typeToAugmentation, typeToDefiningSchema, typeToDefiningSchema.inverse(), identities);
138 public EffectiveModelContext getEffectiveModelContext() {
139 return schemaContext;
142 public Optional<AugmentationSchemaNode> findAugmentation(final Type type) {
143 return Optional.ofNullable(typeToAugmentation.get(type));
146 public Optional<Type> findIdentity(final QName qname) {
147 return Optional.ofNullable(identities.get(qname));
150 public Optional<WithStatus> findSchema(final Type type) {
151 return Optional.ofNullable(typeToSchema.get(type));
154 public Optional<Absolute> findSchemaNodeIdentifier(final Type type) {
155 final ImmutableMap<Type, Absolute> local = (ImmutableMap<Type, Absolute>) TYPE_TO_IDENTIFIER.getAcquire(this);
156 final Absolute existing = local.get(type);
157 return existing != null ? Optional.of(existing) : loadSchemaNodeIdentifier(local, type);
160 public Optional<Type> findType(final WithStatus schema) {
161 return Optional.ofNullable(schemaToType.get(schema));
164 public Optional<Type> findOriginalAugmentationType(final AugmentationSchemaNode augment) {
165 // If the augment statement does not contain any child nodes, we did not generate an augmentation, as it would
166 // be plain littering.
167 // FIXME: MDSAL-695: this check is rather costly (involves filtering), can we just rely on the not being found
168 // in the end? all we are saving is essentially two map lookups after all...
169 if (augment.getChildNodes().isEmpty()) {
170 return Optional.empty();
173 // FIXME: MDSAL-695: We should have enough information from mdsal-binding-generator to receive a (sparse) Map
174 // for current -> original lookup. When combined with schemaToType, this amounts to the
175 // inverse view of what 'typeToSchema' holds
176 AugmentationSchemaNode current = augment;
178 // If this augmentation has been added through 'uses foo { augment bar { ... } }', we need to invert that
179 // walk and arrive at the original declaration site, as that is where we generated 'grouping foo's
180 // augmentation. That site may have a different module, hence the augment namespace may be different.
181 final Optional<AugmentationSchemaNode> original = current.getOriginalDefinition();
182 if (original.isEmpty()) {
183 return findType(current);
185 current = original.orElseThrow();
189 public Multimap<Type, Type> getChoiceToCases() {
190 return choiceToCases;
193 public Collection<Type> findCases(final Type choiceType) {
194 return choiceToCases.get(choiceType);
198 public String toString() {
199 return MoreObjects.toStringHelper(this)
200 .add("typeToAugmentation", typeToAugmentation)
201 .add("typeToSchema", typeToSchema)
202 .add("choiceToCases", choiceToCases)
203 .add("identities", identities)
207 private Optional<Absolute> loadSchemaNodeIdentifier(final ImmutableMap<Type, Absolute> local, final Type type) {
208 final WithStatus schema = typeToSchema.get(type);
209 if (!(schema instanceof SchemaNode)) {
210 return Optional.empty();
213 // TODO: do not rely on getPath() here
214 final Absolute created = Absolute.of(ImmutableList.copyOf(((SchemaNode) schema).getPath().getPathFromRoot()))
217 ImmutableMap<Type, Absolute> prev = local;
219 // Compute next cache
220 final ImmutableMap<Type, Absolute> next =
221 ImmutableMap.<Type, Absolute>builderWithExpectedSize(prev.size() + 1)
223 .put(type, created).build();
225 final Object witness = TYPE_TO_IDENTIFIER.compareAndExchangeRelease(this, prev, next);
226 if (witness == prev) {
227 // Cache populated successfully, we are all done now
228 return Optional.of(created);
231 // Remember cache for next computation
232 prev = (ImmutableMap<Type, Absolute>) witness;
233 final Absolute raced = prev.get(type);
235 // We have raced on this item, use it from cache
236 return Optional.of(raced);
239 // We have raced on a different item, loop around and repeat