Switch NormalizedNode->Binding codegen to ByteBuddy
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataObjectCodecContext.java
index 6a5d26ce4b296665a857a402752a21f385c746cc..6505b3afdb0a3a47deb017f4707e0928165c85c3 100644 (file)
@@ -12,15 +12,14 @@ 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.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -35,13 +34,11 @@ 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.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.yangtools.yang.binding.Augmentable;
 import org.opendaylight.yangtools.yang.binding.Augmentation;
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier.Item;
@@ -63,7 +60,11 @@ import org.opendaylight.yangtools.yang.model.util.SchemaNodeUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeContainer & WithStatus>
+/**
+ * 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<D extends DataObject, T extends DataNodeContainer & WithStatus>
         extends DataContainerCodecContext<D, T> {
     private static final class Augmentations implements Immutable {
         final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYang;
@@ -77,22 +78,25 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     }
 
     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 MethodType CONSTRUCTOR_TYPE = MethodType.methodType(void.class, NormalizedNodeContainer.class);
+    private static final MethodType AUGMENTABLE_CONSTRUCTOR_TYPE = MethodType.methodType(void.class,
+        DataObjectCodecContext.class, NormalizedNodeContainer.class);
+    private static final MethodType DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+        NormalizedNodeContainer.class);
+    private static final MethodType AUGMENTABLE_DATAOBJECT_TYPE = MethodType.methodType(DataObject.class,
+        DataObjectCodecContext.class, NormalizedNodeContainer.class);
     private static final Comparator<Method> 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<String, ValueNodeCodecContext> leafChild;
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
-    private final ImmutableMap<String, NodeContextSupplier> byMethod;
-    private final ImmutableMap<String, String> nonnullToGetter;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byStreamClass;
     private final ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClass;
     private final ImmutableMap<AugmentationIdentifier, Type> possibleAugmentations;
     private final MethodHandle proxyConstructor;
-    private final Method[] propertyMethods;
 
+    // FIXME: the presence of these two volatile fields may be preventing us from being able to improve
+    //        DataContainerCodecPrototype.get() publication.
     @SuppressWarnings("rawtypes")
     private static final AtomicReferenceFieldUpdater<DataObjectCodecContext, Augmentations>
         AUGMENTATIONS_UPDATER = AtomicReferenceFieldUpdater.newUpdater(DataObjectCodecContext.class,
@@ -102,6 +106,11 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     private volatile ImmutableMap<Class<?>, DataContainerCodecPrototype<?>> mismatchedAugmented = ImmutableMap.of();
 
     DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype) {
+        this(prototype, null);
+    }
+
+    DataObjectCodecContext(final DataContainerCodecPrototype<T> prototype,
+            final Entry<Method, IdentifiableItemCodec> keyMethod) {
         super(prototype);
 
         final Class<D> bindingClass = getBindingClass();
@@ -142,58 +151,46 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
             }
         }
 
-        final int methodCount = tmpMethodToSupplier.size();
-        final Builder<String, NodeContextSupplier> byMethodBuilder = ImmutableMap.builderWithExpectedSize(methodCount);
-        this.propertyMethods = methodCount == 0 ? EMPTY_METHODS : new Method[methodCount];
-
-        int offset = 0;
-        for (Entry<Method, NodeContextSupplier> entry : tmpMethodToSupplier.entrySet()) {
-            final Method method = entry.getKey();
-            propertyMethods[offset++] = method;
-            byMethodBuilder.put(method.getName(), entry.getValue());
-        }
-
         // Make sure properties are alpha-sorted
-        Arrays.sort(propertyMethods, METHOD_BY_ALPHABET);
+        final Method[] properties = tmpMethodToSupplier.keySet().toArray(new Method[0]);
+        Arrays.sort(properties, METHOD_BY_ALPHABET);
+        final Builder<Method, NodeContextSupplier> propBuilder = ImmutableMap.builderWithExpectedSize(
+            properties.length);
+        for (Method prop : properties) {
+            propBuilder.put(prop, verifyNotNull(tmpMethodToSupplier.get(prop)));
+        }
 
-        this.byMethod = byMethodBuilder.build();
         this.byYang = ImmutableMap.copyOf(byYangBuilder);
         this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
         byBindingArgClassBuilder.putAll(byStreamClass);
         this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
 
-        final Map<Class<?>, Method> clsToNonnull = BindingReflections.getChildrenClassToNonnullMethod(bindingClass);
-        final Map<String, String> nonnullToGetterBuilder = new HashMap<>();
-        for (final Entry<Class<?>, Method> entry : clsToNonnull.entrySet()) {
-            final Method method = entry.getValue();
-            if (!method.isDefault()) {
-                LOG.warn("Ignoring non-default method {} in {}", method, bindingClass);
-                continue;
-            }
-
-            // 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);
-
+        final Class<? extends CodecDataObject<?>> generatedClass;
+        final MethodType ctorType;
         if (Augmentable.class.isAssignableFrom(bindingClass)) {
             this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
+            generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
+                bindingClass, propBuilder.build(), keyMethod);
+            ctorType = AUGMENTABLE_CONSTRUCTOR_TYPE;
         } else {
             this.possibleAugmentations = ImmutableMap.of();
+            generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
+                propBuilder.build(), keyMethod);
+            ctorType = CONSTRUCTOR_TYPE;
         }
         reloadAllAugmentations();
 
-        final Class<?> proxyClass = Proxy.getProxyClass(bindingClass.getClassLoader(), bindingClass,
-            AugmentationHolder.class);
+        final MethodHandle ctor;
         try {
-            proxyConstructor = MethodHandles.publicLookup().findConstructor(proxyClass, CONSTRUCTOR_TYPE)
-                    .asType(DATAOBJECT_TYPE);
+            ctor = MethodHandles.publicLookup().findConstructor(generatedClass, ctorType);
         } 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);
+        }
+
+        if (Augmentable.class.isAssignableFrom(bindingClass)) {
+            proxyConstructor = ctor.asType(AUGMENTABLE_DATAOBJECT_TYPE).bindTo(this);
+        } else {
+            proxyConstructor = ctor.asType(DATAOBJECT_TYPE);
         }
     }
 
@@ -314,8 +311,8 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         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();
+        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);
@@ -519,29 +516,10 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         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);
-    }
-
-    @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<NormalizedNode<?, ?>> domChild = domData.getChild(childContext.getDomPathArgument());
-
-        // 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();
-    }
-
     @SuppressWarnings("checkstyle:illegalCatch")
     protected final D createBindingProxy(final NormalizedNodeContainer<?, ?, ?> node) {
         try {
-            return (D) proxyConstructor.invokeExact((InvocationHandler)new LazyDataObject<>(this, node));
+            return (D) proxyConstructor.invokeExact(node);
         } catch (final Throwable e) {
             Throwables.throwIfUnchecked(e);
             throw new IllegalStateException(e);
@@ -574,10 +552,6 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         return map;
     }
 
-    final Method[] propertyMethods() {
-        return propertyMethods;
-    }
-
     @Override
     public InstanceIdentifier.PathArgument deserializePathArgument(final YangInstanceIdentifier.PathArgument arg) {
         checkArgument(getDomPathArgument().equals(arg));