Rework BindingRuntimeTypes
[mdsal.git] / binding / mdsal-binding-runtime-api / src / main / java / org / opendaylight / mdsal / binding / runtime / api / AbstractBindingRuntimeContext.java
index f7587ee913330b56327c010e913263794cc779ba..dbed269c4af0d895127351403becc4af7433da88 100644 (file)
 package org.opendaylight.mdsal.binding.runtime.api;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 
 import com.google.common.annotations.Beta;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import java.util.AbstractMap.SimpleEntry;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.MethodSignature;
-import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
-import org.opendaylight.mdsal.binding.model.api.Type;
-import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTypeBuilder;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
 import org.opendaylight.yangtools.yang.binding.Action;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
 import org.opendaylight.yangtools.yang.binding.Notification;
+import org.opendaylight.yangtools.yang.binding.RpcInput;
+import org.opendaylight.yangtools.yang.binding.RpcOutput;
 import org.opendaylight.yangtools.yang.common.QName;
-import org.opendaylight.yangtools.yang.common.QNameModule;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier;
-import org.opendaylight.yangtools.yang.model.api.ActionDefinition;
-import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
-import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DerivableSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
-import org.opendaylight.yangtools.yang.model.api.SchemaNode;
-import org.opendaylight.yangtools.yang.model.util.EffectiveAugmentationSchema;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Absolute;
 
 /**
  * Runtime Context for Java YANG Binding classes. It provides information derived from the backing effective model,
  * which is not captured in generated classes (and hence cannot be obtained from {@code BindingReflections}.
- *
- * <p>Some of this information are for example list of all available children for cases
- * {@link #getChoiceCaseChildren(DataNodeContainer)}, since choices are augmentable and new choices may be introduced
- * by additional models. Same goes for all possible augmentations.
  */
 @Beta
 public abstract class AbstractBindingRuntimeContext implements BindingRuntimeContext {
-    private static final Logger LOG = LoggerFactory.getLogger(AbstractBindingRuntimeContext.class);
-
     private final LoadingCache<QName, Class<?>> identityClasses = CacheBuilder.newBuilder().weakValues().build(
         new CacheLoader<QName, Class<?>>() {
             @Override
             public Class<?> load(final QName key) {
-                final Optional<Type> identityType = getTypes().findIdentity(key);
-                checkArgument(identityType.isPresent(), "Supplied QName %s is not a valid identity", key);
+                final var type = getTypes().findIdentity(key).orElseThrow(
+                    () -> new IllegalArgumentException("Supplied QName " + key + " is not a valid identity"));
                 try {
-                    return loadClass(identityType.get());
+                    return loadClass(type.getIdentifier());
                 } catch (final ClassNotFoundException e) {
-                    throw new IllegalArgumentException("Required class " + identityType + "was not found.", e);
+                    throw new IllegalArgumentException("Required class " + type + " was not found.", e);
                 }
             }
         });
 
     @Override
-    public final <T extends Augmentation<?>> AugmentationSchemaNode getAugmentationDefinition(final Class<T> augClass) {
-        return getTypes().findAugmentation(Type.of(augClass)).orElse(null);
+    public final <T extends Augmentation<?>> AugmentRuntimeType getAugmentationDefinition(final Class<T> augClass) {
+        return getTypes().findSchema(JavaTypeName.create(augClass))
+            .filter(AugmentRuntimeType.class::isInstance)
+            .map(AugmentRuntimeType.class::cast)
+            .orElse(null);
     }
 
     @Override
-    public final DataSchemaNode getSchemaDefinition(final Class<?> cls) {
+    public final CompositeRuntimeType getSchemaDefinition(final Class<?> cls) {
         checkArgument(!Augmentation.class.isAssignableFrom(cls), "Supplied class must not be an augmentation (%s is)",
             cls);
         checkArgument(!Action.class.isAssignableFrom(cls), "Supplied class must not be an action (%s is)", cls);
         checkArgument(!Notification.class.isAssignableFrom(cls), "Supplied class must not be a notification (%s is)",
             cls);
-        return (DataSchemaNode) getTypes().findSchema(Type.of(cls)).orElse(null);
-    }
-
-    @Override
-    public final DataSchemaNode findChildSchemaDefinition(final DataNodeContainer parentSchema,
-            final QNameModule parentNamespace, final Class<?> childClass) {
-        final DataSchemaNode origDef = getSchemaDefinition(childClass);
-        if (origDef == null) {
-            // Weird, the child does not have an associated definition
-            return null;
-        }
-
-        // Direct instantiation or use in same module in which grouping was defined.
-        final QName origName = origDef.getQName();
-        final DataSchemaNode sameName = parentSchema.dataChildByName(origName);
-        if (sameName != null) {
-            // Check if it is:
-            // - exactly same schema node, or
-            // - instantiated node was added via uses statement and is instantiation of same grouping
-            if (origDef.equals(sameName) || origDef.equals(getRootOriginalIfPossible(sameName))) {
-                return sameName;
-            }
-
-            // Node has same name, but clearly is different
-            return null;
-        }
-
-        // We are looking for instantiation via uses in other module
-        final DataSchemaNode potential = parentSchema.dataChildByName(origName.bindTo(parentNamespace));
-        // We check if it is really instantiated from same definition as class was derived
-        if (potential != null && origDef.equals(getRootOriginalIfPossible(potential))) {
-            return potential;
-        }
-        return null;
-    }
-
-    private static @Nullable SchemaNode getRootOriginalIfPossible(final SchemaNode data) {
-        SchemaNode previous = null;
-        SchemaNode next = originalNodeOf(data);
-        while (next != null) {
-            previous = next;
-            next = originalNodeOf(next);
-        }
-        return previous;
+        return (CompositeRuntimeType) getTypes().findSchema(JavaTypeName.create(cls)).orElse(null);
     }
 
     @Override
-    public final ActionDefinition getActionDefinition(final Class<? extends Action<?, ?, ?>> cls) {
-        return (ActionDefinition) getTypes().findSchema(Type.of(cls)).orElse(null);
+    public final ActionRuntimeType getActionDefinition(final Class<? extends Action<?, ?, ?>> cls) {
+        return (ActionRuntimeType) getTypes().findSchema(JavaTypeName.create(cls)).orElse(null);
     }
 
     @Override
-    public final Entry<AugmentationIdentifier, AugmentationSchemaNode> getResolvedAugmentationSchema(
-            final DataNodeContainer target, final Class<? extends Augmentation<?>> aug) {
-        final AugmentationSchemaNode origSchema = getAugmentationDefinition(aug);
-        checkArgument(origSchema != null, "Augmentation %s is not known in current schema context", aug);
-        /*
-         * FIXME: Validate augmentation schema lookup
-         *
-         * Currently this algorithm, does not verify if instantiated child nodes
-         * are real one derived from augmentation schema. The problem with
-         * full validation is, if user used copy builders, he may use
-         * augmentation which was generated for different place.
-         *
-         * If this augmentations have same definition, we emit same identifier
-         * with data and it is up to underlying user to validate data.
-         *
-         */
-        final Set<QName> childNames = new HashSet<>();
-        final Set<DataSchemaNode> realChilds = new HashSet<>();
-        for (final DataSchemaNode child : origSchema.getChildNodes()) {
-            final DataSchemaNode dataChildQNname = target.dataChildByName(child.getQName());
-            final String childLocalName = child.getQName().getLocalName();
-            if (dataChildQNname == null) {
-                for (DataSchemaNode dataSchemaNode : target.getChildNodes()) {
-                    if (childLocalName.equals(dataSchemaNode.getQName().getLocalName())) {
-                        realChilds.add(dataSchemaNode);
-                        childNames.add(dataSchemaNode.getQName());
-                    }
-                }
-            } else {
-                realChilds.add(dataChildQNname);
-                childNames.add(child.getQName());
-            }
-        }
-
-        final AugmentationIdentifier identifier = AugmentationIdentifier.create(childNames);
-        final AugmentationSchemaNode proxy = new EffectiveAugmentationSchema(origSchema, realChilds);
-        return new SimpleEntry<>(identifier, proxy);
+    public final RuntimeType getTypeWithSchema(final Class<?> type) {
+        return getTypes().findSchema(JavaTypeName.create(type))
+            .orElseThrow(() -> new IllegalArgumentException("Failed to find schema for " + type));
     }
 
     @Override
-    public final Optional<CaseSchemaNode> getCaseSchemaDefinition(final ChoiceSchemaNode schema,
-            final Class<?> childClass) {
-        final DataSchemaNode origSchema = getSchemaDefinition(childClass);
-        checkArgument(origSchema instanceof CaseSchemaNode, "Supplied schema %s is not case.", origSchema);
-
-        /* FIXME: Make sure that if there are multiple augmentations of same
-         * named case, with same structure we treat it as equals
-         * this is due property of Binding specification and copy builders
-         * that user may be unaware that he is using incorrect case
-         * which was generated for choice inside grouping.
-         */
-        return findInstantiatedCase(schema, (CaseSchemaNode) origSchema);
+    public final Class<?> getClassForSchema(final Absolute schema) {
+        final var child = getTypes().schemaTreeChild(schema);
+        checkArgument(child != null, "Failed to find binding type for %s", schema);
+        return loadClass(child);
     }
 
     @Override
-    public final Entry<GeneratedType, WithStatus> getTypeWithSchema(final Class<?> type) {
-        return getTypeWithSchema(getTypes(), Type.of(type));
-    }
-
-    private static @NonNull Entry<GeneratedType, WithStatus> getTypeWithSchema(final BindingRuntimeTypes types,
-            final Type referencedType) {
-        final WithStatus schema = types.findSchema(referencedType).orElseThrow(
-            () -> new NullPointerException("Failed to find schema for type " + referencedType));
-        final Type definedType = types.findType(schema).orElseThrow(
-            () -> new NullPointerException("Failed to find defined type for " + referencedType + " schema " + schema));
-
-        if (definedType instanceof GeneratedTypeBuilder) {
-            return new SimpleEntry<>(((GeneratedTypeBuilder) definedType).build(), schema);
-        }
-        checkArgument(definedType instanceof GeneratedType, "Type %s is not a GeneratedType", referencedType);
-        return new SimpleEntry<>((GeneratedType) definedType, schema);
+    public final Class<?> getIdentityClass(final QName input) {
+        return identityClasses.getUnchecked(input);
     }
 
     @Override
-    public final Map<Type, Entry<Type, Type>> getChoiceCaseChildren(final DataNodeContainer schema) {
-        return getChoiceCaseChildren(getTypes(), schema);
-    }
-
-    private static @NonNull ImmutableMap<Type, Entry<Type, Type>> getChoiceCaseChildren(final BindingRuntimeTypes types,
-            final DataNodeContainer schema) {
-        final Map<Type, Entry<Type, Type>> childToCase = new HashMap<>();
-
-        for (final ChoiceSchemaNode choice :  Iterables.filter(schema.getChildNodes(), ChoiceSchemaNode.class)) {
-            final ChoiceSchemaNode originalChoice = getOriginalSchema(choice);
-            final Optional<Type> optType = types.findType(originalChoice);
-            checkState(optType.isPresent(), "Failed to find generated type for choice %s", originalChoice);
-            final Type choiceType = optType.get();
-
-            for (Type caze : types.findCases(choiceType)) {
-                final Entry<Type,Type> caseIdentifier = new SimpleEntry<>(choiceType, caze);
-                final HashSet<Type> caseChildren = new HashSet<>();
-                if (caze instanceof GeneratedTypeBuilder) {
-                    caze = ((GeneratedTypeBuilder) caze).build();
-                }
-                collectAllContainerTypes((GeneratedType) caze, caseChildren);
-                for (final Type caseChild : caseChildren) {
-                    childToCase.put(caseChild, caseIdentifier);
-                }
-            }
-        }
-        return ImmutableMap.copyOf(childToCase);
+    public final Class<? extends RpcInput> getRpcInput(final QName rpcName) {
+        return loadClass(getTypes().findRpcInput(rpcName)
+            .orElseThrow(() -> new IllegalArgumentException("Failed to find RpcInput for " + rpcName)))
+            .asSubclass(RpcInput.class);
     }
 
     @Override
-    public final Set<Class<?>> getCases(final Class<?> choice) {
-        final Collection<Type> cazes = getTypes().findCases(Type.of(choice));
-        final Set<Class<?>> ret = new HashSet<>(cazes.size());
-        for (final Type caze : cazes) {
-            try {
-                ret.add(loadClass(caze));
-            } catch (final ClassNotFoundException e) {
-                LOG.warn("Failed to load class for case {}, ignoring it", caze, e);
-            }
-        }
-        return ret;
+    public final Class<? extends RpcOutput> getRpcOutput(final QName rpcName) {
+        return loadClass(getTypes().findRpcOutput(rpcName)
+            .orElseThrow(() -> new IllegalArgumentException("Failed to find RpcOutput for " + rpcName)))
+            .asSubclass(RpcOutput.class);
     }
 
-    @Override
-    public final Class<?> getClassForSchema(final SchemaNode childSchema) {
-        final SchemaNode origSchema = getOriginalSchema(childSchema);
-        final Optional<Type> clazzType = getTypes().findType(origSchema);
-        checkArgument(clazzType.isPresent(), "Failed to find binding type for %s (original %s)",
-            childSchema, origSchema);
-
+    private Class<?> loadClass(final RuntimeType type) {
         try {
-            return loadClass(clazzType.get());
+            return loadClass(type.javaType());
         } catch (final ClassNotFoundException e) {
             throw new IllegalStateException(e);
         }
     }
-
-    @Override
-    public final ImmutableMap<AugmentationIdentifier, Type> getAvailableAugmentationTypes(
-            final DataNodeContainer container) {
-        if (container instanceof AugmentationTarget) {
-            final var augmentations = ((AugmentationTarget) container).getAvailableAugmentations();
-            if (!augmentations.isEmpty()) {
-                final var identifierToType = new HashMap<AugmentationIdentifier, Type>();
-                final var types = getTypes();
-                for (var augment : augmentations) {
-                    types.findOriginalAugmentationType(augment).ifPresent(augType -> {
-                        identifierToType.put(getAugmentationIdentifier(augment), augType);
-                    });
-                }
-                return ImmutableMap.copyOf(identifierToType);
-            }
-        }
-        return ImmutableMap.of();
-    }
-
-    @Override
-    public final Class<?> getIdentityClass(final QName input) {
-        return identityClasses.getUnchecked(input);
-    }
-
-    private static AugmentationIdentifier getAugmentationIdentifier(final AugmentationSchemaNode augment) {
-        // FIXME: use DataSchemaContextNode.augmentationIdentifierFrom() once it does caching
-        return AugmentationIdentifier.create(augment.getChildNodes().stream().map(DataSchemaNode::getQName)
-            .collect(ImmutableSet.toImmutableSet()));
-    }
-
-    private static Set<Type> collectAllContainerTypes(final GeneratedType type, final Set<Type> collection) {
-        for (final MethodSignature definition : type.getMethodDefinitions()) {
-            Type childType = definition.getReturnType();
-            if (childType instanceof ParameterizedType) {
-                childType = ((ParameterizedType) childType).getActualTypeArguments()[0];
-            }
-            if (childType instanceof GeneratedType || childType instanceof GeneratedTypeBuilder) {
-                collection.add(childType);
-            }
-        }
-        for (final Type parent : type.getImplements()) {
-            if (parent instanceof GeneratedType) {
-                collectAllContainerTypes((GeneratedType) parent, collection);
-            }
-        }
-        return collection;
-    }
-
-    private static <T extends SchemaNode> T getOriginalSchema(final T choice) {
-        @SuppressWarnings("unchecked")
-        final T original = (T) originalNodeOf(choice);
-        if (original != null) {
-            return original;
-        }
-        return choice;
-    }
-
-    private static @NonNull Optional<CaseSchemaNode> findInstantiatedCase(final ChoiceSchemaNode instantiatedChoice,
-            final CaseSchemaNode originalDefinition) {
-        CaseSchemaNode potential = instantiatedChoice.findCase(originalDefinition.getQName()).orElse(null);
-        if (originalDefinition.equals(potential)) {
-            return Optional.of(potential);
-        }
-        if (potential != null) {
-            SchemaNode potentialRoot = originalNodeOf(potential);
-            if (originalDefinition.equals(potentialRoot)) {
-                return Optional.of(potential);
-            }
-        }
-
-        // We try to find case by name, then lookup its root definition
-        // and compare it with original definition
-        // This solves case, if choice was inside grouping
-        // which was used in different module and thus namespaces are
-        // different, but local names are still same.
-        //
-        // Still we need to check equality of definition, because local name is not
-        // sufficient to uniquelly determine equality of cases
-        //
-        for (CaseSchemaNode caze : instantiatedChoice.findCaseNodes(originalDefinition.getQName().getLocalName())) {
-            if (originalDefinition.equals(originalNodeOf(caze))) {
-                return Optional.of(caze);
-            }
-        }
-        return Optional.empty();
-    }
-
-    private static @Nullable SchemaNode originalNodeOf(final SchemaNode node) {
-        return node instanceof DerivableSchemaNode ? ((DerivableSchemaNode) node).getOriginal().orElse(null) : null;
-    }
 }