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=91fe183ec45b87966ff4ea9964ecee8ae3c3cf60;hb=27af6e9fc4d7b3f557ba0a24561afbdbed5a9894;hp=6a5d26ce4b296665a857a402752a21f385c746cc;hpb=cd0e4ce77caa250e64dd7edabfbfc2952c5d6a85;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 6a5d26ce4b..91fe183ec4 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 @@ -8,474 +8,202 @@ 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 static java.util.Objects.requireNonNull; +import com.google.common.annotations.Beta; import com.google.common.base.Throwables; +import com.google.common.base.VerifyException; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableSet; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 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.Arrays; -import java.util.Comparator; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; 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.model.api.JavaTypeName; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode; +import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeCachingCodec; +import org.opendaylight.mdsal.binding.model.api.GeneratedType; import org.opendaylight.mdsal.binding.model.api.Type; -import org.opendaylight.mdsal.binding.spec.naming.BindingMapping; -import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections; -import org.opendaylight.yangtools.concepts.Immutable; -import org.opendaylight.yangtools.util.ClassLoaderUtils; +import org.opendaylight.mdsal.binding.runtime.api.AugmentRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.AugmentableRuntimeType; +import org.opendaylight.mdsal.binding.runtime.api.CompositeRuntimeType; 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.BindingObject; 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.binding.OpaqueObject; +import org.opendaylight.yangtools.yang.binding.DataObjectStep; import org.opendaylight.yangtools.yang.common.QName; -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.DataContainerNode; 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.data.api.schema.builder.DataContainerNodeBuilder; +import org.opendaylight.yangtools.yang.data.impl.schema.Builders; +import org.opendaylight.yangtools.yang.model.api.stmt.SchemaTreeEffectiveStatement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class DataObjectCodecContext - extends DataContainerCodecContext { - private static final class Augmentations implements Immutable { - final ImmutableMap> byYang; - final ImmutableMap, DataContainerCodecPrototype> byStream; +/** + * This class is an implementation detail. It is public only due to technical reasons and may change at any time. + */ +@Beta +public abstract sealed class DataObjectCodecContext + extends AbstractDataObjectCodecContext implements BindingDataObjectCodecTreeNode + permits CaseCodecContext, ContainerLikeCodecContext, ListCodecContext, NotificationCodecContext { + private static final Logger LOG = LoggerFactory.getLogger(DataObjectCodecContext.class); + + private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, + AbstractDataObjectCodecContext.class, DataContainerNode.class); + private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class, + DataObjectCodecContext.class, DataContainerNode.class); + private static final VarHandle MISMATCHED_AUGMENTED; - Augmentations(final ImmutableMap> byYang, - final ImmutableMap, DataContainerCodecPrototype> byStream) { - this.byYang = requireNonNull(byYang); - this.byStream = requireNonNull(byStream); + static { + try { + MISMATCHED_AUGMENTED = MethodHandles.lookup().findVarHandle(DataObjectCodecContext.class, + "mismatchedAugmented", ImmutableMap.class); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new ExceptionInInitializerError(e); } } - 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 Augmentations EMPTY_AUGMENTATIONS = new Augmentations(ImmutableMap.of(), ImmutableMap.of()); - private static final Method[] EMPTY_METHODS = new Method[0]; - - private final ImmutableMap leafChild; - private final ImmutableMap byYang; - private final ImmutableMap byMethod; - private final ImmutableMap nonnullToGetter; - private final ImmutableMap, DataContainerCodecPrototype> byStreamClass; - private final ImmutableMap, DataContainerCodecPrototype> byBindingArgClass; - private final ImmutableMap possibleAugmentations; + private final ImmutableMap, AugmentationCodecPrototype> augmentToPrototype; + private final ImmutableMap> yangToAugmentClass; + private final @NonNull Class> generatedClass; private final MethodHandle proxyConstructor; - private final Method[] propertyMethods; - - @SuppressWarnings("rawtypes") - private static final AtomicReferenceFieldUpdater - AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(DataObjectCodecContext.class, - Augmentations.class, "augmentations"); - private volatile Augmentations augmentations = EMPTY_AUGMENTATIONS; - - private volatile ImmutableMap, DataContainerCodecPrototype> mismatchedAugmented = ImmutableMap.of(); - - DataObjectCodecContext(final DataContainerCodecPrototype prototype) { - super(prototype); - - final Class bindingClass = getBindingClass(); - this.leafChild = factory().getLeafNodes(bindingClass, getSchema()); - final Map, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass); + // 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") + @SuppressFBWarnings(value = "URF_UNREAD_FIELD", justification = "https://github.com/spotbugs/spotbugs/issues/2749") + private volatile ImmutableMap, CommonDataObjectCodecPrototype> mismatchedAugmented = ImmutableMap.of(); - final Map byYangBuilder = new HashMap<>(); - final Map tmpMethodToSupplier = new HashMap<>(); - final Map, DataContainerCodecPrototype> byStreamClassBuilder = new HashMap<>(); - final Map, DataContainerCodecPrototype> byBindingArgClassBuilder = new HashMap<>(); - - // Adds leaves to mapping - for (final ValueNodeCodecContext leaf : leafChild.values()) { - tmpMethodToSupplier.put(leaf.getGetter(), leaf); - byYangBuilder.put(leaf.getDomPathArgument(), leaf); - } - - for (final Entry, Method> childDataObj : clsToMethod.entrySet()) { - final Method method = childDataObj.getValue(); - verify(!method.isDefault(), "Unexpected default method %s in %s", method, bindingClass); + DataObjectCodecContext(final CommonDataObjectCodecPrototype prototype) { + this(prototype, CodecItemFactory.of()); + } - final Class retClass = childDataObj.getKey(); - if (OpaqueObject.class.isAssignableFrom(retClass)) { - // Filter OpaqueObjects, they are not containers - continue; - } + DataObjectCodecContext(final CommonDataObjectCodecPrototype prototype, final CodecItemFactory itemFactory) { + this(prototype, new DataContainerAnalysis<>(prototype, itemFactory), null); + } - final DataContainerCodecPrototype childProto = loadChildPrototype(retClass); - tmpMethodToSupplier.put(method, childProto); - byStreamClassBuilder.put(childProto.getBindingClass(), childProto); - byYangBuilder.put(childProto.getYangArg(), childProto); - if (childProto.isChoice()) { - final ChoiceNodeCodecContext choice = (ChoiceNodeCodecContext) childProto.get(); - for (final Class cazeChild : choice.getCaseChildrenClasses()) { - byBindingArgClassBuilder.put(cazeChild, childProto); - } - } - } + DataObjectCodecContext(final CommonDataObjectCodecPrototype prototype, final Method keyMethod) { + this(prototype, new DataContainerAnalysis<>(prototype, CodecItemFactory.of()), keyMethod); + } - final int methodCount = tmpMethodToSupplier.size(); - final Builder byMethodBuilder = ImmutableMap.builderWithExpectedSize(methodCount); - this.propertyMethods = methodCount == 0 ? EMPTY_METHODS : new Method[methodCount]; + private DataObjectCodecContext(final CommonDataObjectCodecPrototype prototype, + final DataContainerAnalysis analysis, final Method keyMethod) { + super(prototype, analysis); - int offset = 0; - for (Entry entry : tmpMethodToSupplier.entrySet()) { - final Method method = entry.getKey(); - propertyMethods[offset++] = method; - byMethodBuilder.put(method.getName(), entry.getValue()); - } + final var bindingClass = getBindingClass(); - // Make sure properties are alpha-sorted - Arrays.sort(propertyMethods, METHOD_BY_ALPHABET); - - this.byMethod = byMethodBuilder.build(); - 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 nonnullToGetterBuilder = 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 bits: generate the appropriate class, As a side effect we identify what Augmentations are possible + final List possibleAugmentations; + final var loader = prototype().contextFactory().getLoader(); + if (Augmentable.class.isAssignableFrom(bindingClass)) { + // Verify we have the appropriate backing runtimeType + final var runtimeType = prototype.runtimeType(); + if (!(runtimeType instanceof AugmentableRuntimeType augmentableRuntimeType)) { + throw new VerifyException( + "Unexpected type %s backing augmenable %s".formatted(runtimeType, bindingClass)); } - // Derive getter name from the nonnull method and verify we have the corresponding getter. Note that - // the intern() call is important, as it makes sure we use the same instance to bridge to byMethod map. - final String methodName = method.getName(); - final String getterName = BindingMapping.getGetterMethodForNonnull(methodName).intern(); - verify(byMethod.containsKey(getterName), "Cannot find getter %s for %s", getterName, methodName); - nonnullToGetterBuilder.put(methodName, getterName); - } - nonnullToGetter = ImmutableMap.copyOf(nonnullToGetterBuilder); - - if (Augmentable.class.isAssignableFrom(bindingClass)) { - this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema()); + possibleAugmentations = augmentableRuntimeType.augments(); + generatedClass = CodecDataObjectGenerator.generateAugmentable(loader, bindingClass, analysis.leafContexts, + analysis.daoProperties, keyMethod); } else { - this.possibleAugmentations = ImmutableMap.of(); + possibleAugmentations = List.of(); + generatedClass = CodecDataObjectGenerator.generate(loader, bindingClass, analysis.leafContexts, + analysis.daoProperties, keyMethod); } - reloadAllAugmentations(); - final Class proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass, - AugmentationHolder.class); + // All done: acquire the constructor: it is supposed to be public + 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); } - } - - // This method could be synchronized, but that would mean that concurrent attempts to load an invalid augmentation - // would end up being unnecessarily contended -- blocking real progress and not being able to run concurrently - // while producing no effect. We therefore use optimistic read + CAS. - private void reloadAllAugmentations() { - // Load current values - Augmentations oldAugmentations = augmentations; - - // FIXME: can we detect when we have both maps fully populated and skip all of this? - // Scratch space for additions - final Map> addByYang = new HashMap<>(); - final Map, DataContainerCodecPrototype> addByStream = new HashMap<>(); + proxyConstructor = ctor.asType(DATAOBJECT_TYPE); - // Iterate over all possibilities, checking for modifications. - for (final Type augment : possibleAugmentations.values()) { - final DataContainerCodecPrototype augProto = getAugmentationPrototype(augment); + // Deal with augmentations, which are not something we analysis provides + final var augPathToBinding = new HashMap>(); + final var augClassToProto = new HashMap, AugmentationCodecPrototype>(); + for (var augment : possibleAugmentations) { + final var augProto = loadAugmentPrototype(augment); if (augProto != null) { - final PathArgument yangArg = augProto.getYangArg(); - final Class bindingClass = augProto.getBindingClass(); - if (!oldAugmentations.byYang.containsKey(yangArg)) { - if (addByYang.putIfAbsent(yangArg, augProto) == null) { - LOG.trace("Discovered new YANG mapping {} -> {} in {}", yangArg, augProto, this); - } + final var augBindingClass = augProto.javaClass(); + for (var childPath : augProto.getChildArgs()) { + augPathToBinding.putIfAbsent(childPath, augBindingClass); } - if (!oldAugmentations.byStream.containsKey(bindingClass)) { - if (addByStream.putIfAbsent(bindingClass, augProto) == null) { - LOG.trace("Discovered new class mapping {} -> {} in {}", bindingClass, augProto, this); - } - } - } - } - - while (true) { - if (addByYang.isEmpty() && addByStream.isEmpty()) { - LOG.trace("No new augmentations discovered in {}", this); - return; + augClassToProto.putIfAbsent(augBindingClass, augProto); } - - // We have some additions, propagate them out - final Augmentations newAugmentations = new Augmentations(concatMaps(oldAugmentations.byYang, addByYang), - concatMaps(oldAugmentations.byStream, addByStream)); - if (AUGMENTATIONS_UPDATER.compareAndSet(this, oldAugmentations, newAugmentations)) { - // Success, we are done - return; - } - - // We have raced installing new augmentations, read them again, remove everything present in the installed - // once and try again. This may mean that we end up not doing anything, but that's fine. - oldAugmentations = augmentations; - - // We could use Map.removeAll(oldAugmentations.byYang.keySet()), but that forces the augmentation's keyset - // to be materialized, which we otherwise do not need. Hence we do this the other way around, instantiating - // our temporary maps' keySets and iterating over them. That's fine as we'll be throwing those maps away. - removeMapKeys(addByYang, oldAugmentations.byYang); - removeMapKeys(addByStream, oldAugmentations.byStream); } + yangToAugmentClass = ImmutableMap.copyOf(augPathToBinding); + augmentToPrototype = ImmutableMap.copyOf(augClassToProto); } - private static ImmutableMap concatMaps(final ImmutableMap old, final Map add) { - if (add.isEmpty()) { - return old; - } - - final Builder builder = ImmutableMap.builderWithExpectedSize(old.size() + add.size()); - builder.putAll(old); - builder.putAll(add); - return builder.build(); - } - - private static void removeMapKeys(final Map removeFrom, final ImmutableMap map) { - final Iterator it = removeFrom.keySet().iterator(); - while (it.hasNext()) { - if (map.containsKey(it.next())) { - it.remove(); - } - } - } - - @SuppressWarnings("unchecked") @Override - public DataContainerCodecContext streamChild(final Class childClass) { - final DataContainerCodecPrototype childProto = streamChildPrototype(childClass); - return (DataContainerCodecContext) childNonNull(childProto, childClass, " Child %s is not valid child.", - childClass).get(); + final DataContainerPrototype pathChildPrototype(final Class argType) { + final var child = super.pathChildPrototype(argType); + return child != null ? child : augmentToPrototype.get(argType); } - private DataContainerCodecPrototype streamChildPrototype(final Class childClass) { - final DataContainerCodecPrototype childProto = byStreamClass.get(childClass); - if (childProto != null) { - return childProto; - } - if (Augmentation.class.isAssignableFrom(childClass)) { - return augmentationByClass(childClass); - } - return null; - } - - @SuppressWarnings("unchecked") @Override - public Optional> possibleStreamChild( - final Class childClass) { - final DataContainerCodecPrototype childProto = streamChildPrototype(childClass); - if (childProto != null) { - return Optional.of((DataContainerCodecContext) childProto.get()); + final DataContainerPrototype streamChildPrototype(final Class childClass) { + final var child = super.streamChildPrototype(childClass); + if (child == null && Augmentation.class.isAssignableFrom(childClass)) { + return getAugmentationProtoByClass(childClass); } - return Optional.empty(); + return child; } @Override - public DataContainerCodecContext bindingPathArgumentChild(final InstanceIdentifier.PathArgument arg, - final List builder) { - - final Class argType = arg.getType(); - DataContainerCodecPrototype ctxProto = byBindingArgClass.get(argType); - if (ctxProto == null && Augmentation.class.isAssignableFrom(argType)) { - ctxProto = augmentationByClass(argType); - } - 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); - - final Optional> caseType = arg.getCaseType(); - final Class type = arg.getType(); - final DataContainerCodecContext caze; - if (caseType.isPresent()) { - // Non-ambiguous addressing this should not pose any problems - caze = choice.streamChild(caseType.get()); - } else { - caze = choice.getCaseByChildClass(type); + final CodecContextSupplier yangChildSupplier(final NodeIdentifier arg) { + final var child = super.yangChildSupplier(arg); + if (child == null) { + final var augClass = yangToAugmentClass.get(arg); + if (augClass != null) { + return augmentToPrototype.get(augClass); } - - caze.addYangPathArgument(arg, builder); - return caze.bindingPathArgumentChild(arg, builder); } - context.addYangPathArgument(arg, builder); - return context; + return child; } - @Override - 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); - } else { - childSupplier = byYang.get(arg); - } - - return childNonNull(childSupplier, arg, "Argument %s is not valid child of %s", arg, getSchema()).get(); - } - - protected final ValueNodeCodecContext getLeafChild(final String name) { - final ValueNodeCodecContext value = leafChild.get(name); - return IncorrectNestingException.checkNonNull(value, "Leaf %s is not valid for %s", name, getBindingClass()); - } - - 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()); - } - - @SuppressWarnings("unchecked") - Item createBindingArg(final Class childClass, final DataSchemaNode childSchema) { - return Item.of((Class) childClass); + private @Nullable AugmentationCodecPrototype getAugmentationProtoByClass(final @NonNull Class augmClass) { + final var childProto = augmentToPrototype.get(augmClass); + return childProto != null ? childProto : mismatchedAugmentationByClass(augmClass); } - private DataContainerCodecPrototype yangAugmentationChild(final AugmentationIdentifier arg) { - final DataContainerCodecPrototype firstTry = augmentations.byYang.get(arg); - if (firstTry != null) { - return firstTry; - } - if (possibleAugmentations.containsKey(arg)) { - // Try to load augmentations, which will potentially update knownAugmentations, hence we re-load that field - // again. - reloadAllAugmentations(); - return augmentations.byYang.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; - } - - private @Nullable DataContainerCodecPrototype augmentationByClassOrEquivalentClass( - final @NonNull Class childClass) { - // Perform a single load, so we can reuse it if we end up going to the reflection-based slow path - final ImmutableMap, DataContainerCodecPrototype> local = augmentations.byStream; - final DataContainerCodecPrototype childProto = local.get(childClass); - if (childProto != null) { - return childProto; - } - + private @Nullable AugmentationCodecPrototype 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 var local = (ImmutableMap, AugmentationCodecPrototype>) MISMATCHED_AUGMENTED.getAcquire(this); + final var mismatched = local.get(childClass); + return mismatched != null ? mismatched : loadMismatchedAugmentation(local, childClass); + } + private @Nullable AugmentationCodecPrototype loadMismatchedAugmentation( + final ImmutableMap, AugmentationCodecPrototype> oldMismatched, + final @NonNull Class childClass) { @SuppressWarnings("rawtypes") - final Class augTarget = BindingReflections.findAugmentationTarget((Class) childClass); - if (getBindingClass().equals(augTarget)) { - for (final DataContainerCodecPrototype realChild : local.values()) { - if (Augmentation.class.isAssignableFrom(realChild.getBindingClass()) - && BindingReflections.isSubstitutionFor(childClass, realChild.getBindingClass())) { - return cacheMismatched(childClass, realChild); + final Class augTarget = findAugmentationTarget((Class) childClass); + // 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 (var realChild : augmentToPrototype.values()) { + final var realClass = realChild.javaClass(); + if (Augmentation.class.isAssignableFrom(realClass) && isSubstitutionFor(childClass, realClass)) { + return cacheMismatched(oldMismatched, childClass, realChild); } } } @@ -483,110 +211,135 @@ abstract class DataObjectCodecContext 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 AugmentationCodecPrototype cacheMismatched( + final @NonNull ImmutableMap, AugmentationCodecPrototype> oldMismatched, + final @NonNull Class childClass, final @NonNull AugmentationCodecPrototype prototype) { + var expected = oldMismatched; + while (true) { + final var newMismatched = + ImmutableMap., CommonDataObjectCodecPrototype>builderWithExpectedSize(expected.size() + 1) + .putAll(expected) + .put(childClass, prototype) + .build(); + + final var witness = (ImmutableMap, AugmentationCodecPrototype>) + MISMATCHED_AUGMENTED.compareAndExchangeRelease(this, expected, newMismatched); + if (witness == expected) { + LOG.trace("Cached mismatched augmentation {} -> {} in {}", childClass, prototype, this); + return prototype; + } - mismatchedAugmented = builder.build(); - LOG.trace("Cached mismatched augmentation {} -> {} in {}", childClass, prototype, this); - return prototype; + expected = witness; + final var 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 var ctx = prototype().contextFactory().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()); - } - - // Unlike BindingMapping.getGetterMethodForNonnull() this returns an interned String - @NonNull String getterNameForNonnullName(final String nonnullMethod) { - return verifyNotNull(nonnullToGetter.get(nonnullMethod), "Failed to look up getter method for %s", - nonnullMethod); + return cls.equals(loaded); } - @SuppressWarnings("rawtypes") - @Nullable Object getBindingChildValue(final String method, final NormalizedNodeContainer domData) { - final NodeCodecContext childContext = verifyNotNull(byMethod.get(method), - "Cannot find data handler for method %s", method).get(); - - @SuppressWarnings("unchecked") - final Optional> domChild = domData.getChild(childContext.getDomPathArgument()); + private @Nullable AugmentationCodecPrototype loadAugmentPrototype(final AugmentRuntimeType augment) { + // FIXME: in face of deviations this code should be looking at declared view, i.e. all possibilities at augment + // declaration site + final var childPaths = augment.statement() + .streamEffectiveSubstatements(SchemaTreeEffectiveStatement.class) + .map(stmt -> new NodeIdentifier((QName) stmt.argument())) + .collect(ImmutableSet.toImmutableSet()); - // 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()) : childContext.defaultObject(); - } + if (childPaths.isEmpty()) { + return null; + } - @SuppressWarnings("checkstyle:illegalCatch") - protected final D createBindingProxy(final NormalizedNodeContainer node) { + final var factory = prototype().contextFactory(); + final GeneratedType javaType = augment.javaType(); + final Class> augClass; try { - return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node)); - } catch (final Throwable e) { - Throwables.throwIfUnchecked(e); - throw new IllegalStateException(e); + augClass = factory.getRuntimeContext().loadClass(javaType); + } catch (final ClassNotFoundException e) { + throw new IllegalStateException( + "RuntimeContext references type " + javaType + " but failed to load its class", e); } + return new AugmentationCodecPrototype<>(augClass, augment, factory, childPaths); } + @Override @SuppressWarnings("unchecked") - Map>, Augmentation> getAllAugmentationsFrom( - final NormalizedNodeContainer> data) { - - @SuppressWarnings("rawtypes") - final Map map = new HashMap<>(); - - for (final NormalizedNode childValue : data.getValue()) { - if (childValue instanceof AugmentationNode) { - final AugmentationNode augDomNode = (AugmentationNode) childValue; - final DataContainerCodecPrototype codecProto = yangAugmentationChild(augDomNode.getIdentifier()); - if (codecProto != null) { - final DataContainerCodecContext codec = codecProto.get(); - map.put(codec.getBindingClass(), codec.deserializeObject(augDomNode)); - } + Map>, Augmentation> getAllAugmentationsFrom(final DataContainerNode data) { + /** + * Due to augmentation fields are at same level as direct children the data of each augmentation needs to be + * aggregated into own container node, then only deserialized using associated prototype. + */ + final var builders = new HashMap, DataContainerNodeBuilder>(); + for (var childValue : data.body()) { + final var bindingClass = yangToAugmentClass.get(childValue.name()); + if (bindingClass != null) { + builders.computeIfAbsent(bindingClass, + key -> Builders.containerBuilder() + .withNodeIdentifier(new NodeIdentifier(data.name().getNodeType()))) + .addChild(childValue); } } - for (final DataContainerCodecPrototype value : augmentations.byStream.values()) { - final Optional> augData = data.getChild(value.getYangArg()); - if (augData.isPresent()) { - map.put(value.getBindingClass(), value.get().deserializeObject(augData.get())); + @SuppressWarnings("rawtypes") + final var map = new HashMap(); + for (final var entry : builders.entrySet()) { + final var bindingClass = entry.getKey(); + final var codecProto = augmentToPrototype.get(bindingClass); + if (codecProto != null) { + final var bindingObj = codecProto.getCodecContext().deserializeObject(entry.getValue().build()); + if (bindingObj != null) { + map.put(bindingClass, bindingObj); + } } } return map; } - final Method[] propertyMethods() { - return propertyMethods; - } - @Override - public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) { + public DataObjectStep deserializePathArgument(final PathArgument arg) { checkArgument(getDomPathArgument().equals(arg)); return bindingArg(); } @Override - public YangInstanceIdentifier.PathArgument serializePathArgument(final InstanceIdentifier.PathArgument arg) { - checkArgument(bindingArg().equals(arg)); + public PathArgument serializePathArgument(final DataObjectStep step) { + checkArgument(bindingArg().equals(step)); return getDomPathArgument(); } + + @Override + public NormalizedNode serialize(final D data) { + return serializeImpl(data); + } + + @Override + public final BindingNormalizedNodeCachingCodec createCachingCodec( + final ImmutableCollection> cacheSpecifier) { + return createCachingCodec(this, cacheSpecifier); + } + + final @NonNull Class> generatedClass() { + return generatedClass; + } + + @SuppressWarnings("checkstyle:illegalCatch") + final @NonNull D createBindingProxy(final DataContainerNode node) { + try { + return (D) proxyConstructor.invokeExact(this, node); + } catch (final Throwable e) { + Throwables.throwIfUnchecked(e); + throw new IllegalStateException(e); + } + } }