*/
package org.opendaylight.mdsal.binding.generator.impl;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
@NonNull BindingRuntimeTypes toTypeMapping() {
final Map<Type, AugmentationSchemaNode> augmentationToSchema = new HashMap<>();
- final BiMap<Type, WithStatus> typeToDefiningSchema = HashBiMap.create();
+ final Map<Type, WithStatus> typeToSchema = new HashMap<>();
final Multimap<Type, Type> choiceToCases = HashMultimap.create();
final Map<QName, Type> identities = new HashMap<>();
+ // Note: we are keying through WithStatus, but these nodes compare on semantics, so equivalent schema nodes
+ // can result in two distinct types. We certainly need to keep them separate.
+ final Map<WithStatus, Type> schemaToType = new IdentityHashMap<>();
+
/*
- * Fun parts are here. ModuleContext maps have Builders in them, we want plan types. We may encounter each
+ * Fun parts are here. ModuleContext maps have Builders in them, we want plain types. We may encounter each
* builder multiple times, hence we keep a builder->instance cache.
*/
final Map<Type, Type> builderToType = new IdentityHashMap<>();
+
for (final ModuleContext ctx : moduleContexts()) {
for (Entry<Type, AugmentationSchemaNode> e : ctx.getTypeToAugmentation().entrySet()) {
augmentationToSchema.put(builtType(builderToType, e.getKey()), e.getValue());
}
for (Entry<Type, WithStatus> e : ctx.getTypeToSchema().entrySet()) {
- typeToDefiningSchema.put(builtType(builderToType, e.getKey()), e.getValue());
+ final Type type = builtType(builderToType, e.getKey());
+ typeToSchema.put(type, e.getValue());
+ schemaToType.put(e.getValue(), type);
}
for (Entry<Type, Type> e : ctx.getChoiceToCases().entries()) {
choiceToCases.put(builtType(builderToType, e.getKey()), builtType(builderToType, e.getValue()));
}
}
- return new BindingRuntimeTypes(schemaContext(), augmentationToSchema, typeToDefiningSchema, choiceToCases,
+ return new BindingRuntimeTypes(schemaContext(), augmentationToSchema, typeToSchema, schemaToType, choiceToCases,
identities);
}
--- /dev/null
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.junit.Test;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.test.util.YangParserTestUtils;
+
+@NonNullByDefault
+public class Mdsal600Test {
+ private static final JavaTypeName FOO = JavaTypeName.create("org.opendaylight.yang.gen.v1.mdsal600.norev", "Foo");
+
+ @Test
+ public void mdsal600Test() {
+ final var models = YangParserTestUtils.parseYangResource("/mdsal600.yang");
+ final var module = models.getModules().iterator().next();
+ final var fooSchema = (ContainerSchemaNode) module.findDataTreeChild(QName.create("mdsal600", "foo"))
+ .orElseThrow();
+
+ // Verify leaves
+ final var barSchema = fooSchema.findDataTreeChild(QName.create("mdsal600", "bar")).orElseThrow();
+ final var bazSchema = fooSchema.findDataTreeChild(QName.create("mdsal600", "baz")).orElseThrow();
+ final var barTypeSchema = ((LeafSchemaNode) barSchema).getType();
+ final var bazTypeSchema = ((LeafSchemaNode) bazSchema).getType();
+
+ // Precondition to our bug: the two types compare as equal, but are not the same
+ assertEquals(barTypeSchema, bazTypeSchema);
+ assertNotSame(barTypeSchema, bazTypeSchema);
+
+ // Quick check for codegen, not really interesting
+ final var generatedTypes = DefaultBindingGenerator.generateFor(models);
+ assertEquals(2, generatedTypes.size());
+
+ // The real thing, used to kaboom
+ final var runtimeTypes = new DefaultBindingRuntimeGenerator().generateTypeMapping(models);
+
+ // Verify schema-to-type lookup
+ final var barType = runtimeTypes.findType(barTypeSchema).orElseThrow();
+ final var bazType = runtimeTypes.findType(bazTypeSchema).orElseThrow();
+ assertEquals(FOO.createEnclosed("Bar"), barType.getIdentifier());
+ assertEquals(FOO.createEnclosed("Baz"), bazType.getIdentifier());
+
+ // Verify type-to-schema lookup
+ assertSame(barTypeSchema, runtimeTypes.findSchema(barType).orElseThrow());
+ assertSame(bazTypeSchema, runtimeTypes.findSchema(bazType).orElseThrow());
+ }
+}
--- /dev/null
+module mdsal600 {
+ prefix mdsal600;
+ namespace mdsal600;
+
+ container foo {
+ leaf bar {
+ type enumeration {
+ enum foo {
+ value 0;
+ description "does not even matter";
+ }
+ enum bar {
+ value 1;
+ }
+ }
+ }
+
+ leaf baz {
+ type enumeration {
+ enum foo {
+ value 0;
+ }
+ enum bar {
+ value 1;
+ reference "is useless as well";
+ }
+ }
+ }
+ }
+}
+
import com.google.common.annotations.Beta;
import com.google.common.base.MoreObjects;
import com.google.common.collect.BiMap;
-import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Collection;
+import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.yangtools.yang.model.api.EffectiveModelContextProvider;
import org.opendaylight.yangtools.yang.model.api.SchemaNode;
import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* The result of BindingGenerator run. Contains mapping between Types and SchemaNodes.
*/
@Beta
public final class BindingRuntimeTypes implements EffectiveModelContextProvider, Immutable {
+ private static final Logger LOG = LoggerFactory.getLogger(BindingRuntimeTypes.class);
private static final VarHandle TYPE_TO_IDENTIFIER;
static {
private final @NonNull EffectiveModelContext schemaContext;
private final ImmutableMap<Type, AugmentationSchemaNode> typeToAugmentation;
- private final ImmutableBiMap<Type, WithStatus> typeToSchema;
+ private final ImmutableMap<Type, WithStatus> typeToSchema;
private final ImmutableMultimap<Type, Type> choiceToCases;
private final ImmutableMap<QName, Type> identities;
+ // Not Immutable as we use two different implementations
+ private final Map<WithStatus, Type> schemaToType;
@SuppressWarnings("unused")
// Accessed via TYPE_TO_IDENTIFIER
public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
final Map<Type, AugmentationSchemaNode> typeToAugmentation,
- final BiMap<Type, WithStatus> typeToDefiningSchema, final Multimap<Type, Type> choiceToCases,
- final Map<QName, Type> identities) {
+ final Map<Type, WithStatus> typeToSchema, final Map<WithStatus, Type> schemaToType,
+ final Multimap<Type, Type> choiceToCases, final Map<QName, Type> identities) {
this.schemaContext = requireNonNull(schemaContext);
this.typeToAugmentation = ImmutableMap.copyOf(typeToAugmentation);
- this.typeToSchema = ImmutableBiMap.copyOf(typeToDefiningSchema);
+ this.typeToSchema = ImmutableMap.copyOf(typeToSchema);
this.choiceToCases = ImmutableMultimap.copyOf(choiceToCases);
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<WithStatus, Type> copy;
+ try {
+ copy = ImmutableMap.copyOf(schemaToType);
+ } catch (IllegalArgumentException e) {
+ LOG.debug("Equality-duplicates found in {}", schemaToType.keySet());
+ copy = new IdentityHashMap<>(schemaToType);
+ }
+
+ this.schemaToType = copy;
+ }
+
+ public BindingRuntimeTypes(final EffectiveModelContext schemaContext,
+ final Map<Type, AugmentationSchemaNode> typeToAugmentation,
+ final BiMap<Type, WithStatus> typeToDefiningSchema,
+ final Multimap<Type, Type> choiceToCases, final Map<QName, Type> identities) {
+ this(schemaContext, typeToAugmentation, typeToDefiningSchema, typeToDefiningSchema.inverse(), choiceToCases,
+ identities);
}
@Override
}
public Optional<Type> findType(final WithStatus schema) {
- return Optional.ofNullable(typeToSchema.inverse().get(schema));
+ return Optional.ofNullable(schemaToType.get(schema));
}
public Multimap<Type, Type> getChoiceToCases() {
--- /dev/null
+module mdsal600 {
+ prefix mdsal600;
+ namespace mdsal600;
+
+ container foo {
+ leaf bar {
+ type enumeration {
+ enum foo {
+ value 0;
+ description "does not even matter";
+ }
+ enum bar {
+ value 1;
+ }
+ }
+ }
+
+ leaf baz {
+ type enumeration {
+ enum foo {
+ value 0;
+ }
+ enum bar {
+ value 1;
+ reference "is useless as well";
+ }
+ }
+ }
+ }
+}
+