X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-dom-codec%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fdom%2Fcodec%2Fimpl%2FDataObjectCodecContext.java;h=47575f9321d707a46eeb43a1276d983e76127bb5;hb=0ee55d1f9da11dd03ff05fc67d10cbcbfe63fd2c;hp=774573a9f76a25b77eff59e971206e3ae415c341;hpb=9aa1ca4f5147192fe204f5c069e7d25ac8fb4c60;p=mdsal.git diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java index 774573a9f7..47575f9321 100644 --- a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java +++ b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java @@ -9,175 +9,208 @@ package org.opendaylight.mdsal.binding.dom.codec.impl; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Verify.verify; -import static com.google.common.base.Verify.verifyNotNull; +import com.google.common.annotations.Beta; import com.google.common.base.Throwables; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; -import com.google.common.collect.ImmutableSortedMap; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import com.google.common.collect.ImmutableSet; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.InvocationHandler; +import java.lang.invoke.VarHandle; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.Collection; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.function.Function; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy; +import org.opendaylight.mdsal.binding.dom.codec.api.IncorrectNestingException; +import org.opendaylight.mdsal.binding.model.api.GeneratedType; import org.opendaylight.mdsal.binding.model.api.JavaTypeName; import org.opendaylight.mdsal.binding.model.api.Type; +import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.AugmentableRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.BindingRuntimeContext; +import org.opendaylight.mdsal.binding.runtime.api.ChoiceRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType; import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; -import org.opendaylight.yangtools.util.ClassLoaderUtils; import org.opendaylight.yangtools.yang.binding.Augmentable; import org.opendaylight.yangtools.yang.binding.Augmentation; -import org.opendaylight.yangtools.yang.binding.AugmentationHolder; +import org.opendaylight.yangtools.yang.binding.DataContainer; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item; -import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.binding.OpaqueObject; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.AugmentationIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; +import org.opendaylight.yangtools.yang.data.api.schema.DistinctNodeContainer; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer; -import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; -import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus; -import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils; +import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class DataObjectCodecContext +/** + * This class is an implementation detail. It is public only due to technical reasons and may change at any time. + */ +@Beta +public abstract class DataObjectCodecContext extends DataContainerCodecContext { private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class); - private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, InvocationHandler.class); - private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, InvocationHandler.class); - private static final Comparator METHOD_BY_ALPHABET = Comparator.comparing(Method::getName); + private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, + DataObjectCodecContext.class, DistinctNodeContainer.class); + private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, + DataObjectCodecContext.class, DistinctNodeContainer.class); + private static final VarHandle MISMATCHED_AUGMENTED; + + static { + try { + MISMATCHED_AUGMENTED = MethodHandles.lookup().findVarHandle(DataObjectCodecContext.class, + "mismatchedAugmented", ImmutableMap.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); + } + } - private final ImmutableMap> leafChild; + private final ImmutableMap leafChild; private final ImmutableMap byYang; - private final ImmutableSortedMap byMethod; - private final ImmutableMap nonnullMethods; private final ImmutableMap, DataContainerCodecPrototype> byStreamClass; private final ImmutableMap, DataContainerCodecPrototype> byBindingArgClass; - private final ImmutableMap possibleAugmentations; + private final ImmutableMap> augmentationByYang; + private final ImmutableMap, DataContainerCodecPrototype> augmentationByStream; + private final @NonNull Class> generatedClass; private final MethodHandle proxyConstructor; - private final ConcurrentMap> byYangAugmented = - new ConcurrentHashMap<>(); - private final ConcurrentMap, DataContainerCodecPrototype> byStreamAugmented = new ConcurrentHashMap<>(); - + // Note this the content of this field depends only of invariants expressed as this class's fields or + // BindingRuntimeContext. It is only accessed via MISMATCHED_AUGMENTED above. + @SuppressWarnings("unused") private volatile ImmutableMap, DataContainerCodecPrototype> mismatchedAugmented = ImmutableMap.of(); DataObjectCodecContext(final DataContainerCodecPrototype prototype) { + this(prototype, null); + } + + DataObjectCodecContext(final DataContainerCodecPrototype prototype, final Method keyMethod) { super(prototype); final Class bindingClass = getBindingClass(); - this.leafChild = factory().getLeafNodes(bindingClass, getSchema()); - final Map, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass); + final ImmutableMap tmpLeaves = factory().getLeafNodes(bindingClass, + getType().statement()); + final Map, Method> clsToMethod = + BindingReflections.getChildrenClassToMethod(bindingClass); final Map byYangBuilder = new HashMap<>(); - final SortedMap byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET); final Map, DataContainerCodecPrototype> byStreamClassBuilder = new HashMap<>(); final Map, DataContainerCodecPrototype> byBindingArgClassBuilder = new HashMap<>(); // Adds leaves to mapping - for (final LeafNodeCodecContext leaf : leafChild.values()) { - byMethodBuilder.put(leaf.getGetter(), leaf); + final Builder leafChildBuilder = + ImmutableMap.builderWithExpectedSize(tmpLeaves.size()); + for (final ValueNodeCodecContext leaf : tmpLeaves.values()) { + leafChildBuilder.put(leaf.getSchema().getQName().getLocalName(), leaf); byYangBuilder.put(leaf.getDomPathArgument(), leaf); } + this.leafChild = leafChildBuilder.build(); - for (final Entry, Method> childDataObj : clsToMethod.entrySet()) { + final Map> tmpDataObjects = new HashMap<>(); + for (final Entry, Method> childDataObj : clsToMethod.entrySet()) { final Method method = childDataObj.getValue(); verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass); - final DataContainerCodecPrototype childProto = loadChildPrototype(childDataObj.getKey()); - byMethodBuilder.put(method, childProto); - byStreamClassBuilder.put(childProto.getBindingClass(), childProto); + + final Class retClass = childDataObj.getKey(); + if (OpaqueObject.class.isAssignableFrom(retClass)) { + // Filter OpaqueObjects, they are not containers + continue; + } + + final DataContainerCodecPrototype childProto = loadChildPrototype(retClass); + final Class childClass = childProto.getBindingClass(); + tmpDataObjects.put(method, childClass); + byStreamClassBuilder.put(childClass, childProto); byYangBuilder.put(childProto.getYangArg(), childProto); - if (childProto.isChoice()) { + + // FIXME: It really feels like we should be specializing DataContainerCodecPrototype so as to ditch + // createInstance() and then we could do an instanceof check instead. + if (childProto.getType() instanceof ChoiceRuntimeType) { final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) childProto.get(); for (final Class cazeChild : choice.getCaseChildrenClasses()) { byBindingArgClassBuilder.put(cazeChild, childProto); } } } - this.byMethod = ImmutableSortedMap.copyOfSorted(byMethodBuilder); + this.byYang = ImmutableMap.copyOf(byYangBuilder); this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder); - byBindingArgClassBuilder.putAll(byStreamClass); - this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder); - - final Map, Method> clsToNonnull = BindingReflections.getChildrenClassToNonnullMethod(bindingClass); - final Map nonnullMethodsBuilder = new HashMap<>(); - for (final Entry, Method> entry : clsToNonnull.entrySet()) { - final Method method = entry.getValue(); - if (!method.isDefault()) { - LOG.warn("Ignoring non-default method {} in {}", method, bindingClass); - continue; - } - final DataContainerCodecPrototype supplier = byStreamClass.get(entry.getKey()); - if (supplier != null) { - nonnullMethodsBuilder.put(method, supplier); - } else { - LOG.warn("Failed to look up data handler for method {}", method); - } - } - nonnullMethods = ImmutableMap.copyOf(nonnullMethodsBuilder); + // Slight footprint optimization: we do not want to copy byStreamClass, as that would force its entrySet view + // to be instantiated. Furthermore the two maps can easily end up being equal -- hence we can reuse + // byStreamClass for the purposes of both. + byBindingArgClassBuilder.putAll(byStreamClassBuilder); + this.byBindingArgClass = byStreamClassBuilder.equals(byBindingArgClassBuilder) ? this.byStreamClass + : ImmutableMap.copyOf(byBindingArgClassBuilder); + final List possibleAugmentations; if (Augmentable.class.isAssignableFrom(bindingClass)) { - this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema()); + // Verify we have the appropriate backing runtimeType + final var type = getType(); + verify(type instanceof AugmentableRuntimeType, "Unexpected type %s backing augmenable %s", type, + bindingClass); + possibleAugmentations = ((AugmentableRuntimeType) type).augments(); + generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(), + bindingClass, tmpLeaves, tmpDataObjects, keyMethod); } else { - this.possibleAugmentations = ImmutableMap.of(); + possibleAugmentations = List.of(); + generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass, + tmpLeaves, tmpDataObjects, keyMethod); } - reloadAllAugmentations(); - final Class proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass, - AugmentationHolder.class); + // Iterate over all possible augmentations, indexing them as needed + final Map> augByYang = new HashMap<>(); + final Map, DataContainerCodecPrototype> augByStream = new HashMap<>(); + for (final AugmentRuntimeType augment : possibleAugmentations) { + final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment); + final PathArgument augYangArg = augProto.getYangArg(); + if (augByYang.putIfAbsent(augYangArg, augProto) == null) { + LOG.trace("Discovered new YANG mapping {} -> {} in {}", augYangArg, augProto, this); + } + final Class augBindingClass = augProto.getBindingClass(); + if (augByStream.putIfAbsent(augBindingClass, augProto) == null) { + LOG.trace("Discovered new class mapping {} -> {} in {}", augBindingClass, augProto, this); + } + } + augmentationByYang = ImmutableMap.copyOf(augByYang); + augmentationByStream = ImmutableMap.copyOf(augByStream); + + final MethodHandle ctor; try { - proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE) - .asType(DATAOBJECT_TYPE); + ctor = MethodHandles.publicLookup().findConstructor(generatedClass, CONSTRUCTOR_TYPE); } catch (NoSuchMethodException | IllegalAccessException e) { - throw new IllegalStateException("Failed to find contructor for class " + proxyClass, e); + throw new LinkageError("Failed to find contructor for class " + generatedClass, e); } + + proxyConstructor = ctor.asType(DATAOBJECT_TYPE); } - @SuppressFBWarnings("RV_RETURN_VALUE_OF_PUTIFABSENT_IGNORED") - private void reloadAllAugmentations() { - for (final Type augment : possibleAugmentations.values()) { - final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment); - if (augProto != null) { - byYangAugmented.putIfAbsent(augProto.getYangArg(), augProto); - byStreamAugmented.putIfAbsent(augProto.getBindingClass(), augProto); - } - } + @Override + public final WithStatus getSchema() { + // FIXME: Bad cast, we should be returning an EffectiveStatement perhaps? + return (WithStatus) getType().statement(); } - @SuppressWarnings("unchecked") @Override + @SuppressWarnings("unchecked") public DataContainerCodecContext streamChild(final Class childClass) { - final DataContainerCodecPrototype childProto = streamChildPrototype(childClass); - return (DataContainerCodecContext) childNonNull(childProto, childClass, " Child %s is not valid child.", - childClass).get(); + return (DataContainerCodecContext) childNonNull(streamChildPrototype(childClass), childClass, + "Child %s is not valid child of %s", getBindingClass(), childClass).get(); } private DataContainerCodecPrototype streamChildPrototype(final Class childClass) { @@ -211,8 +244,8 @@ abstract class DataObjectCodecContext context = - childNonNull(ctxProto, argType, "Class %s is not valid child of %s", argType, getBindingClass()).get(); + final DataContainerCodecContext context = childNonNull(ctxProto, argType, + "Class %s is not valid child of %s", argType, getBindingClass()).get(); if (context instanceof ChoiceNodeCodecContext) { final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) context; choice.addYangPathArgument(arg, builder); @@ -234,242 +267,189 @@ abstract class DataObjectCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { + public NodeCodecContext yangPathArgumentChild(final YangInstanceIdentifier.PathArgument arg) { final NodeContextSupplier childSupplier; if (arg instanceof NodeIdentifierWithPredicates) { childSupplier = byYang.get(new NodeIdentifier(arg.getNodeType())); } else if (arg instanceof AugmentationIdentifier) { - childSupplier = yangAugmentationChild((AugmentationIdentifier) arg); + childSupplier = augmentationByYang.get(arg); } else { childSupplier = byYang.get(arg); } - return (NodeCodecContext) childNonNull(childSupplier, arg, - "Argument %s is not valid child of %s", arg, getSchema()).get(); + return childNonNull(childSupplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).get(); } - protected final LeafNodeCodecContext getLeafChild(final String name) { - final LeafNodeCodecContext value = leafChild.get(name); - return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass()); + protected final ValueNodeCodecContext getLeafChild(final String name) { + final ValueNodeCodecContext value = leafChild.get(name); + if (value == null) { + throw IncorrectNestingException.create("Leaf %s is not valid for %s", name, getBindingClass()); + } + return value; } - private DataContainerCodecPrototype loadChildPrototype(final Class childClass) { - final DataSchemaNode origDef = factory().getRuntimeContext().getSchemaDefinition(childClass); - // Direct instantiation or use in same module in which grouping - // was defined. - DataSchemaNode sameName; - try { - sameName = getSchema().getDataChildByName(origDef.getQName()); - } catch (final IllegalArgumentException e) { - sameName = null; - } - final DataSchemaNode childSchema; - if (sameName != null) { - // Exactly same schema node - if (origDef.equals(sameName)) { - childSchema = sameName; - // We check if instantiated node was added via uses - // statement and is instantiation of same grouping - } else if (origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(sameName))) { - childSchema = sameName; - } else { - // Node has same name, but clearly is different - childSchema = null; - } - } else { - // We are looking for instantiation via uses in other module - final QName instantiedName = origDef.getQName().withModule(namespace()); - final DataSchemaNode potential = getSchema().getDataChildByName(instantiedName); - // We check if it is really instantiated from same - // definition as class was derived - if (potential != null && origDef.equals(SchemaNodeUtils.getRootOriginalIfPossible(potential))) { - childSchema = potential; - } else { - childSchema = null; - } - } - final DataSchemaNode nonNullChild = - childNonNull(childSchema, childClass, "Node %s does not have child named %s", getSchema(), childClass); - return DataContainerCodecPrototype.from(createBindingArg(childClass, nonNullChild), nonNullChild, factory()); + private DataContainerCodecPrototype loadChildPrototype(final Class childClass) { + final var type = getType(); + final var child = childNonNull(type.bindingChild(JavaTypeName.create(childClass)), childClass, + "Node %s does not have child named %s", type, childClass); + + return DataContainerCodecPrototype.from(createBindingArg(childClass, child.statement()), + (CompositeRuntimeType) child, factory()); } + // FIXME: MDSAL-697: move this method into BindingRuntimeContext + // This method is only called from loadChildPrototype() and exists only to be overridden by + // CaseNodeCodecContext. Since we are providing childClass and our schema to BindingRuntimeContext + // and receiving childSchema from it via findChildSchemaDefinition, we should be able to receive + // the equivalent of Map.Entry, along with the override we create here. One + // more input we may need to provide is our bindingClass(). @SuppressWarnings("unchecked") - Item createBindingArg(final Class childClass, final DataSchemaNode childSchema) { + Item createBindingArg(final Class childClass, final EffectiveStatement childSchema) { return Item.of((Class) childClass); } - private DataContainerCodecPrototype yangAugmentationChild(final AugmentationIdentifier arg) { - final DataContainerCodecPrototype firstTry = byYangAugmented.get(arg); - if (firstTry != null) { - return firstTry; - } - if (possibleAugmentations.containsKey(arg)) { - reloadAllAugmentations(); - return byYangAugmented.get(arg); - } - return null; - } - private @Nullable DataContainerCodecPrototype augmentationByClass(final @NonNull Class childClass) { - DataContainerCodecPrototype lookup = augmentationByClassOrEquivalentClass(childClass); - if (lookup != null || !isPotentialAugmentation(childClass)) { - return lookup; - } - - // Attempt to reload all augmentations using TCCL and lookup again - reloadAllAugmentations(); - lookup = augmentationByClassOrEquivalentClass(childClass); - if (lookup != null) { - return lookup; - } - - // Still no result, this can be caused by TCCL not being set up properly -- try the class's ClassLoader - // if it is present; - final ClassLoader loader = childClass.getClassLoader(); - if (loader == null) { - return null; - } - - LOG.debug("Class {} not loaded via TCCL, attempting to recover", childClass); - ClassLoaderUtils.runWithClassLoader(loader, this::reloadAllAugmentations); - return augmentationByClassOrEquivalentClass(childClass); - } - - private boolean isPotentialAugmentation(final Class childClass) { - final JavaTypeName name = JavaTypeName.create(childClass); - for (Type type : possibleAugmentations.values()) { - if (name.equals(type.getIdentifier())) { - return true; - } - } - return false; + final DataContainerCodecPrototype childProto = augmentationByStream.get(childClass); + return childProto != null ? childProto : mismatchedAugmentationByClass(childClass); } - private @Nullable DataContainerCodecPrototype augmentationByClassOrEquivalentClass( - final @NonNull Class childClass) { - final DataContainerCodecPrototype childProto = byStreamAugmented.get(childClass); - if (childProto != null) { - return childProto; - } - + private @Nullable DataContainerCodecPrototype mismatchedAugmentationByClass(final @NonNull Class childClass) { /* * It is potentially mismatched valid augmentation - we look up equivalent augmentation using reflection * and walk all stream child and compare augmentations classes if they are equivalent. When we find a match * we'll cache it so we do not need to perform reflection operations again. */ - final DataContainerCodecPrototype mismatched = mismatchedAugmented.get(childClass); - if (mismatched != null) { - return mismatched; - } + final ImmutableMap, DataContainerCodecPrototype> local = + (ImmutableMap, DataContainerCodecPrototype>) MISMATCHED_AUGMENTED.getAcquire(this); + final DataContainerCodecPrototype mismatched = local.get(childClass); + return mismatched != null ? mismatched : loadMismatchedAugmentation(local, childClass); + + } + private @Nullable DataContainerCodecPrototype loadMismatchedAugmentation( + final ImmutableMap, DataContainerCodecPrototype> oldMismatched, + final @NonNull Class childClass) { @SuppressWarnings("rawtypes") final Class augTarget = BindingReflections.findAugmentationTarget((Class) childClass); - if (getBindingClass().equals(augTarget)) { - for (final DataContainerCodecPrototype realChild : byStreamAugmented.values()) { + // Do not bother with proposals which are not augmentations of our class, or do not match what the runtime + // context would load. + if (getBindingClass().equals(augTarget) && belongsToRuntimeContext(childClass)) { + for (final DataContainerCodecPrototype realChild : augmentationByStream.values()) { if (Augmentation.class.isAssignableFrom(realChild.getBindingClass()) && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) { - return cacheMismatched(childClass, realChild); + return cacheMismatched(oldMismatched, childClass, realChild); } } } + LOG.trace("Failed to resolve {} as a valid augmentation in {}", childClass, this); return null; } - private synchronized DataContainerCodecPrototype cacheMismatched(final Class childClass, - final DataContainerCodecPrototype prototype) { - // Original access was unsynchronized, we need to perform additional checking - final ImmutableMap, DataContainerCodecPrototype> local = mismatchedAugmented; - final DataContainerCodecPrototype existing = local.get(childClass); - if (existing != null) { - return existing; - } - - final Builder, DataContainerCodecPrototype> builder = ImmutableMap.builderWithExpectedSize( - local.size() + 1); - builder.putAll(local); - builder.put(childClass, prototype); + private @NonNull DataContainerCodecPrototype cacheMismatched( + final @NonNull ImmutableMap, DataContainerCodecPrototype> oldMismatched, + final @NonNull Class childClass, final @NonNull DataContainerCodecPrototype prototype) { + + ImmutableMap, DataContainerCodecPrototype> expected = oldMismatched; + while (true) { + final Map, DataContainerCodecPrototype> newMismatched = + ImmutableMap., DataContainerCodecPrototype>builderWithExpectedSize(expected.size() + 1) + .putAll(expected) + .put(childClass, prototype) + .build(); + + final var witness = (ImmutableMap, DataContainerCodecPrototype>) + MISMATCHED_AUGMENTED.compareAndExchangeRelease(this, expected, newMismatched); + if (witness == expected) { + LOG.trace("Cached mismatched augmentation {} -> {} in {}", childClass, prototype, this); + return prototype; + } - mismatchedAugmented = builder.build(); - return prototype; + expected = witness; + final DataContainerCodecPrototype existing = expected.get(childClass); + if (existing != null) { + LOG.trace("Using raced mismatched augmentation {} -> {} in {}", childClass, existing, this); + return existing; + } + } } - private DataContainerCodecPrototype getAugmentationPrototype(final Type value) { - final ClassLoadingStrategy loader = factory().getRuntimeContext().getStrategy(); - @SuppressWarnings("rawtypes") - final Class augClass; + private boolean belongsToRuntimeContext(final Class cls) { + final BindingRuntimeContext ctx = factory().getRuntimeContext(); + final Class loaded; try { - augClass = loader.loadClass(value); - } catch (final ClassNotFoundException e) { - LOG.debug("Failed to load augmentation prototype for {}. Will be retried when needed.", value, e); - return null; + loaded = ctx.loadClass(Type.of(cls)); + } catch (ClassNotFoundException e) { + LOG.debug("Proposed {} cannot be loaded in {}", cls, ctx, e); + return false; } - - @SuppressWarnings("unchecked") - final Entry augSchema = factory().getRuntimeContext() - .getResolvedAugmentationSchema(getSchema(), augClass); - return DataContainerCodecPrototype.from(augClass, augSchema.getKey(), augSchema.getValue(), factory()); - } - - Object getBindingChildValue(final Method method, final NormalizedNodeContainer domData) { - return method.isDefault() ? getBindingChildValue(nonnullMethods, method, domData, dummy -> ImmutableList.of()) - : getBindingChildValue(byMethod, method, domData, NodeCodecContext::defaultObject); + return cls.equals(loaded); } - @SuppressWarnings("rawtypes") - private static Object getBindingChildValue(final ImmutableMap map, final Method method, - final NormalizedNodeContainer domData, final Function, Object> getDefaultObject) { - final NodeCodecContext childContext = verifyNotNull(map.get(method), - "Cannot find data handler for method %s", method).get(); + private @NonNull DataContainerCodecPrototype getAugmentationPrototype(final AugmentRuntimeType augment) { + final BindingRuntimeContext ctx = factory().getRuntimeContext(); - @SuppressWarnings("unchecked") - final Optional> domChild = domData.getChild(childContext.getDomPathArgument()); + final GeneratedType javaType = augment.javaType(); + final Class> augClass; + try { + augClass = ctx.loadClass(javaType); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException( + "RuntimeContext references type " + javaType + " but failed to load its class", e); + } - // We do not want to use Optional.map() here because we do not want to invoke defaultObject() when we have - // normal value because defaultObject() may end up throwing an exception intentionally. - return domChild.isPresent() ? childContext.deserializeObject(domChild.get()) - : getDefaultObject.apply(childContext); + // TODO: at some point we need the effective children + return DataContainerCodecPrototype.from(augClass, new AugmentationIdentifier(augment.statement() + .streamEffectiveSubstatements(SchemaTreeEffectiveStatement.class) + .map(SchemaTreeEffectiveStatement::getIdentifier) + .collect(ImmutableSet.toImmutableSet())), augment, factory()); } @SuppressWarnings("checkstyle:illegalCatch") - protected final D createBindingProxy(final NormalizedNodeContainer node) { + protected final @NonNull D createBindingProxy(final DistinctNodeContainer node) { try { - return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node)); + return (D) proxyConstructor.invokeExact(this, node); } catch (final Throwable e) { Throwables.throwIfUnchecked(e); - throw new RuntimeException(e); + throw new IllegalStateException(e); } } @SuppressWarnings("unchecked") Map>, Augmentation> getAllAugmentationsFrom( - final NormalizedNodeContainer> data) { + final DistinctNodeContainer data) { @SuppressWarnings("rawtypes") final Map map = new HashMap<>(); - for (final NormalizedNode childValue : data.getValue()) { + for (final NormalizedNode childValue : data.body()) { if (childValue instanceof AugmentationNode) { final AugmentationNode augDomNode = (AugmentationNode) childValue; - final DataContainerCodecPrototype codecProto = yangAugmentationChild(augDomNode.getIdentifier()); + final DataContainerCodecPrototype codecProto = augmentationByYang.get(augDomNode.getIdentifier()); if (codecProto != null) { final DataContainerCodecContext codec = codecProto.get(); map.put(codec.getBindingClass(), codec.deserializeObject(augDomNode)); } } } - for (final DataContainerCodecPrototype value : byStreamAugmented.values()) { - final Optional> augData = data.getChild(value.getYangArg()); - if (augData.isPresent()) { - map.put(value.getBindingClass(), value.get().deserializeObject(augData.get())); + for (final DataContainerCodecPrototype value : augmentationByStream.values()) { + final var augClass = value.getBindingClass(); + // Do not perform duplicate deserialization if we have already created the corresponding augmentation + // and validate whether the proposed augmentation is valid ion this instantiation context. + if (!map.containsKey(augClass) + && ((AugmentableRuntimeType) getType()).augments().contains(value.getType())) { + final NormalizedNode augData = data.childByArg(value.getYangArg()); + if (augData != null) { + // ... make sure we do not replace an e + map.putIfAbsent(augClass, value.get().deserializeObject(augData)); + } } } return map; } - Collection getHashCodeAndEqualsMethods() { - return byMethod.keySet(); + final @NonNull Class> generatedClass() { + return generatedClass; } @Override