Index getter methods by String 42/78042/5
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 16 Nov 2018 09:20:45 +0000 (10:20 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 22 Nov 2018 13:50:16 +0000 (14:50 +0100)
Looking up by String offers slight advantage over Method lookup,
because we'll end up with a constant hashCode() over objects which
are guaranteed to be interned.

This rework implies we no longer get to have a map whose keySet
reflects property methods -- which is fine, as the keyset would
take up more memory than a plain array, so we retain the methods
in an array -- hence this ends up being a net win in both cases.

JIRA: MDSAL-398
Change-Id: I36526ce15735d4ef8e4ba847bcc24ea69b950e94
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 4c0eb46c1924ca71bb4092b0c91fceb5b790e3f8)

binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/DataObjectCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/KeyedListNodeCodecContext.java
binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/LazyDataObject.java

index 16a786f42fbca87a1c4777ca1e23567a73e15a48..85cf08cc78861d348b6515b943ea75c3dfb19905 100644 (file)
@@ -14,21 +14,18 @@ import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
-import com.google.common.collect.ImmutableSortedMap;
 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.Collection;
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.SortedMap;
-import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 import javax.annotation.Nonnull;
@@ -67,14 +64,16 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     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> METHOD_BY_ALPHABET = Comparator.comparing(Method::getName);
+    private static final Method[] EMPTY_METHODS = new Method[0];
 
     private final ImmutableMap<String, LeafNodeCodecContext<?>> leafChild;
     private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
-    private final ImmutableSortedMap<Method, NodeContextSupplier> byMethod;
+    private final ImmutableMap<String, NodeContextSupplier> byMethod;
     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;
 
     private final ConcurrentMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYangAugmented =
             new ConcurrentHashMap<>();
@@ -91,19 +90,19 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         final Map<Class<?>, Method> clsToMethod = BindingReflections.getChildrenClassToMethod(bindingClass);
 
         final Map<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYangBuilder = new HashMap<>();
-        final SortedMap<Method, NodeContextSupplier> byMethodBuilder = new TreeMap<>(METHOD_BY_ALPHABET);
+        final Map<Method, NodeContextSupplier> tmpMethodToSupplier = new HashMap<>();
         final Map<Class<?>, DataContainerCodecPrototype<?>> byStreamClassBuilder = new HashMap<>();
         final Map<Class<?>, DataContainerCodecPrototype<?>> byBindingArgClassBuilder = new HashMap<>();
 
         // Adds leaves to mapping
         for (final LeafNodeCodecContext<?> leaf : leafChild.values()) {
-            byMethodBuilder.put(leaf.getGetter(), leaf);
+            tmpMethodToSupplier.put(leaf.getGetter(), leaf);
             byYangBuilder.put(leaf.getDomPathArgument(), leaf);
         }
 
         for (final Entry<Class<?>, Method> childDataObj : clsToMethod.entrySet()) {
             final DataContainerCodecPrototype<?> childProto = loadChildPrototype(childDataObj.getKey());
-            byMethodBuilder.put(childDataObj.getValue(), childProto);
+            tmpMethodToSupplier.put(childDataObj.getValue(), childProto);
             byStreamClassBuilder.put(childProto.getBindingClass(), childProto);
             byYangBuilder.put(childProto.getYangArg(), childProto);
             if (childProto.isChoice()) {
@@ -113,7 +112,22 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
                 }
             }
         }
-        this.byMethod = ImmutableSortedMap.copyOfSorted(byMethodBuilder);
+
+        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);
+
+        this.byMethod = byMethodBuilder.build();
         this.byYang = ImmutableMap.copyOf(byYangBuilder);
         this.byStreamClass = ImmutableMap.copyOf(byStreamClassBuilder);
         byBindingArgClassBuilder.putAll(byStreamClass);
@@ -388,7 +402,7 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
     }
 
     @SuppressWarnings("rawtypes")
-    Object getBindingChildValue(final Method method, final NormalizedNodeContainer domData) {
+    @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")
@@ -435,8 +449,8 @@ abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeCo
         return map;
     }
 
-    Collection<Method> getHashCodeAndEqualsMethods() {
-        return byMethod.keySet();
+    final Method[] propertyMethods() {
+        return propertyMethods;
     }
 
     @Override
index f9d5a9ae4836f3bee4164dee7989b9318a578cdb..7ac98b040dee728e694048257477f930bc12c553 100644 (file)
@@ -7,9 +7,9 @@
  */
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
-import java.lang.reflect.Method;
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.IDENTIFIABLE_KEY_NAME;
+
 import java.util.List;
-import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
 import org.opendaylight.yangtools.concepts.Codec;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.Identifiable;
@@ -24,7 +24,6 @@ import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 
 final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> extends ListNodeCodecContext<D> {
     private final Codec<NodeIdentifierWithPredicates, IdentifiableItem<?, ?>> codec;
-    private final Method keyGetter;
 
     KeyedListNodeCodecContext(final DataContainerCodecPrototype<ListSchemaNode> prototype) {
         super(prototype);
@@ -32,7 +31,8 @@ final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> ex
         final Class<D> bindingClass = getBindingClass();
         this.codec = factory().getPathArgumentCodec(bindingClass, getSchema());
         try {
-            this.keyGetter = bindingClass.getMethod(BindingMapping.IDENTIFIABLE_KEY_NAME);
+            // This just verifies the method is present
+            bindingClass.getMethod(IDENTIFIABLE_KEY_NAME);
         } catch (NoSuchMethodException e) {
             throw new IllegalStateException("Required method not available", e);
         }
@@ -60,12 +60,12 @@ final class KeyedListNodeCodecContext<D extends DataObject & Identifiable<?>> ex
 
     @Override
     @SuppressWarnings("rawtypes")
-    Object getBindingChildValue(final Method method, final NormalizedNodeContainer dom) {
-        if (dom instanceof MapEntryNode && keyGetter.equals(method)) {
+    Object getBindingChildValue(final String methodName, final NormalizedNodeContainer dom) {
+        if (dom instanceof MapEntryNode && IDENTIFIABLE_KEY_NAME.equals(methodName)) {
             NodeIdentifierWithPredicates identifier = ((MapEntryNode) dom).getIdentifier();
             return codec.deserialize(identifier).getKey();
         }
-        return super.getBindingChildValue(method, dom);
+        return super.getBindingChildValue(methodName, dom);
     }
 
     @Override
index 680d54f948fd63a26f63bbc6c8856ea94cad8d4a..d2225821a9244ed7b57afba48d8a0d0c46f77989 100644 (file)
@@ -24,6 +24,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.util.AugmentationReader;
 import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
@@ -42,8 +43,10 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
     private static final String EQUALS = "equals";
     private static final String HASHCODE = "hashCode";
     private static final String AUGMENTATIONS = "augmentations";
-    private static final Object NULL_VALUE = new Object();
+    private static final @NonNull Object NULL_VALUE = new Object();
 
+    // Method.getName() is guaranteed to be interned and all getter methods have zero arguments, name is sufficient to
+    // identify the data, skipping Method.hashCode() computation.
     private final ConcurrentHashMap<String, Object> cachedData = new ConcurrentHashMap<>();
     private final NormalizedNodeContainer<?, PathArgument, NormalizedNode<?, ?>> data;
     private final DataObjectCodecContext<D,?> context;
@@ -64,7 +67,8 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
     public Object invoke(final Object proxy, final Method method, final Object[] args) {
         switch (method.getParameterCount()) {
             case 0:
-                switch (method.getName()) {
+                final String methodName = method.getName();
+                switch (methodName) {
                     case DATA_CONTAINER_GET_IMPLEMENTED_INTERFACE_NAME:
                         return context.getBindingClass();
                     case TO_STRING:
@@ -74,7 +78,7 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
                     case AUGMENTATIONS:
                         return getAugmentationsImpl();
                     default:
-                        return getBindingData(method);
+                        return getBindingData(methodName);
                 }
             case 1:
                 switch (method.getName()) {
@@ -102,13 +106,13 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
             return false;
         }
         try {
-            for (final Method m : context.getHashCodeAndEqualsMethods()) {
-                final Object thisValue = getBindingData(m);
+            for (final Method m : context.propertyMethods()) {
+                final Object thisValue = getBindingData(m.getName());
                 final Object otherValue = m.invoke(other);
                 /*
-                *   added for valid byte array comparison, when list key type is binary
-                *   deepEquals is not used since it does excessive amount of instanceof calls.
-                */
+                 *   added for valid byte array comparison, when list key type is binary
+                 *   deepEquals is not used since it does excessive amount of instanceof calls.
+                 */
                 if (thisValue instanceof byte[] && otherValue instanceof byte[]) {
                     if (!Arrays.equals((byte[]) thisValue, (byte[]) otherValue)) {
                         return false;
@@ -148,8 +152,8 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
 
         final int prime = 31;
         int result = 1;
-        for (final Method m : context.getHashCodeAndEqualsMethods()) {
-            final Object value = getBindingData(m);
+        for (final Method m : context.propertyMethods()) {
+            final Object value = getBindingData(m.getName());
             result = prime * result + Objects.hashCode(value);
         }
         if (Augmentable.class.isAssignableFrom(context.getBindingClass())) {
@@ -160,22 +164,21 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
         return ret;
     }
 
-    private Object getBindingData(final Method method) {
-        // Guaranteed to be interned and since method has zero arguments, name is sufficient to identify the data,
-        // skipping Method.hashCode() computation.
-        final String methodName = method.getName();
-        Object cached = cachedData.get(methodName);
-        if (cached == null) {
-            final Object readedValue = context.getBindingChildValue(method, data);
-            if (readedValue == null) {
-                cached = NULL_VALUE;
-            } else {
-                cached = readedValue;
-            }
-            cachedData.putIfAbsent(methodName, cached);
+    // Internal invocation, can only target getFoo() methods
+    private Object getBindingData(final String methodName) {
+        final Object cached = cachedData.get(methodName);
+        if (cached != null) {
+            return unmaskNull(cached);
         }
 
-        return cached == NULL_VALUE ? null : cached;
+        final Object value = context.getBindingChildValue(methodName, data);
+        final Object raced = cachedData.putIfAbsent(methodName, value == null ? NULL_VALUE : value);
+        // If we raced we need to return previously-stored value
+        return raced != null ? unmaskNull(raced) : value;
+    }
+
+    private static Object unmaskNull(final @NonNull Object masked) {
+        return masked == NULL_VALUE ? null : masked;
     }
 
     private Map<Class<? extends Augmentation<?>>, Augmentation<?>> getAugmentationsImpl() {
@@ -227,8 +230,9 @@ class LazyDataObject<D extends DataObject> implements InvocationHandler, Augment
         final Class<D> bindingClass = context.getBindingClass();
         final ToStringHelper helper = MoreObjects.toStringHelper(bindingClass).omitNullValues();
 
-        for (final Method m : context.getHashCodeAndEqualsMethods()) {
-            helper.add(m.getName(), getBindingData(m));
+        for (final Method m : context.propertyMethods()) {
+            final String methodName = m.getName();
+            helper.add(methodName, getBindingData(methodName));
         }
         if (Augmentable.class.isAssignableFrom(bindingClass)) {
             helper.add("augmentations", getAugmentationsImpl());