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;
- }
}