2367c33d52ff1422cb28cd9cadf513f732a31d6d
[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.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;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Optional;
30 import java.util.Set;
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;
47
48 /**
49  * The result of BindingGenerator run. Contains mapping between Types and SchemaNodes.
50  */
51 @Beta
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;
55
56     static {
57         try {
58             TYPE_TO_IDENTIFIER = MethodHandles.lookup().findVarHandle(BindingRuntimeTypes.class, "typeToIdentifier",
59                 ImmutableMap.class);
60         } catch (NoSuchFieldException | IllegalAccessException e) {
61             throw new ExceptionInInitializerError(e);
62         }
63     }
64
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;
72
73     @SuppressWarnings("unused")
74     // Accessed via TYPE_TO_IDENTIFIER
75     private volatile ImmutableMap<Type, Absolute> typeToIdentifier = ImmutableMap.of();
76
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);
85
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;
89         try {
90             copy = ImmutableMap.copyOf(schemaToType);
91         } catch (IllegalArgumentException e) {
92             LOG.debug("Equality-duplicates found in {}", schemaToType.keySet());
93             copy = new IdentityHashMap<>(schemaToType);
94         }
95
96         this.schemaToType = copy;
97
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)
102             .map(entry -> {
103                 final Type key = entry.getKey();
104                 verify(key instanceof GeneratedType, "Unexpected choice type %s", key);
105                 return (GeneratedType) key;
106             })
107             .collect(Collectors.toUnmodifiableSet());
108
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);
116             }
117         }
118
119         choiceToCases = ImmutableMultimap.copyOf(builder);
120     }
121
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;
126             }
127         }
128         return null;
129     }
130
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);
135     }
136
137     @Override
138     public EffectiveModelContext getEffectiveModelContext() {
139         return schemaContext;
140     }
141
142     public Optional<AugmentationSchemaNode> findAugmentation(final Type type) {
143         return Optional.ofNullable(typeToAugmentation.get(type));
144     }
145
146     public Optional<Type> findIdentity(final QName qname) {
147         return Optional.ofNullable(identities.get(qname));
148     }
149
150     public Optional<WithStatus> findSchema(final Type type) {
151         return Optional.ofNullable(typeToSchema.get(type));
152     }
153
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);
158     }
159
160     public Optional<Type> findType(final WithStatus schema) {
161         return Optional.ofNullable(schemaToType.get(schema));
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
182     private Optional<Absolute> loadSchemaNodeIdentifier(final ImmutableMap<Type, Absolute> local, final Type type) {
183         final WithStatus schema = typeToSchema.get(type);
184         if (!(schema instanceof SchemaNode)) {
185             return Optional.empty();
186         }
187
188         // TODO: do not rely on getPath() here
189         final Absolute created = Absolute.of(ImmutableList.copyOf(((SchemaNode) schema).getPath().getPathFromRoot()))
190                 .intern();
191
192         ImmutableMap<Type, Absolute> prev = local;
193         while (true) {
194             // Compute next cache
195             final ImmutableMap<Type, Absolute> next =
196                     ImmutableMap.<Type, Absolute>builderWithExpectedSize(prev.size() + 1)
197                         .putAll(prev)
198                         .put(type, created).build();
199
200             final Object witness = TYPE_TO_IDENTIFIER.compareAndExchangeRelease(this, prev, next);
201             if (witness == prev) {
202                 // Cache populated successfully, we are all done now
203                 return Optional.of(created);
204             }
205
206             // Remember cache for next computation
207             prev = (ImmutableMap<Type, Absolute>) witness;
208             final Absolute raced = prev.get(type);
209             if (raced != null) {
210                 // We have raced on this item, use it from cache
211                 return Optional.of(raced);
212             }
213
214             // We have raced on a different item, loop around and repeat
215         }
216     }
217 }