Split up DataObjectCodecPrototype
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / LazyBindingList.java
1 /*
2  * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.VisibleForTesting;
14 import java.lang.invoke.MethodHandles;
15 import java.lang.invoke.VarHandle;
16 import java.util.AbstractList;
17 import java.util.Collection;
18 import java.util.Comparator;
19 import java.util.List;
20 import java.util.RandomAccess;
21 import java.util.function.UnaryOperator;
22 import org.eclipse.jdt.annotation.NonNull;
23 import org.opendaylight.yangtools.concepts.Immutable;
24 import org.opendaylight.yangtools.yang.binding.DataObject;
25 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodeContainer;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * Lazily-populated List implementation backed by NormalizedNodes. This implementation defers creating Binding objects
32  * until they are actually needed, caching them in a pre-allocated array.
33  *
34  * <p>
35  * The cost of this deferred instantiation is two-fold:
36  * <ul>
37  *   <li>each access issues a {@link VarHandle#getAcquire(Object...)} load and a class equality check</li>
38  *   <li>initial load additionally incurs a {@link VarHandle#compareAndExchangeRelease(Object...)} store</li>
39  * </ul>
40  *
41  * @param <E> the type of elements in this list
42  */
43 final class LazyBindingList<E extends DataObject> extends AbstractList<E> implements Immutable, RandomAccess {
44     // Object array access variable handle
45     static final VarHandle OBJ_AA = MethodHandles.arrayElementVarHandle(Object[].class);
46
47     private static final Logger LOG = LoggerFactory.getLogger(LazyBindingList.class);
48     private static final String LAZY_CUTOFF_PROPERTY =
49             "org.opendaylight.mdsal.binding.dom.codec.impl.LazyBindingList.max-eager-elements";
50     private static final int DEFAULT_LAZY_CUTOFF = 16;
51
52     @VisibleForTesting
53     static final int LAZY_CUTOFF;
54
55     static {
56         final int value = Integer.getInteger(LAZY_CUTOFF_PROPERTY, DEFAULT_LAZY_CUTOFF);
57         if (value < 0) {
58             LOG.info("Lazy population of lists disabled");
59             LAZY_CUTOFF = Integer.MAX_VALUE;
60         } else {
61             LOG.info("Using lazy population for lists larger than {} element(s)", value);
62             LAZY_CUTOFF = value;
63         }
64     }
65
66     private final ListCodecContext<E> codec;
67     private final Object[] objects;
68
69     private LazyBindingList(final ListCodecContext<E> codec,
70             final Collection<? extends NormalizedNodeContainer<?>> entries) {
71         this.codec = requireNonNull(codec);
72         objects = entries.toArray();
73     }
74
75     static <E extends DataObject> @NonNull List<E> create(final ListCodecContext<E> codec, final int size,
76             final Collection<? extends DataContainerNode> entries) {
77         if (size == 1) {
78             // Do not bother with lazy instantiation in case of a singleton
79             return List.of(codec.createBindingProxy(entries.iterator().next()));
80         }
81         return size > LAZY_CUTOFF ? new LazyBindingList<>(codec, entries) : eagerList(codec, size, entries);
82     }
83
84     private static <E extends DataObject> @NonNull List<E> eagerList(final ListCodecContext<E> codec,
85             final int size, final Collection<? extends DataContainerNode> entries) {
86         @SuppressWarnings("unchecked")
87         final E[] objs = (E[]) new DataObject[size];
88         int offset = 0;
89         for (var node : entries) {
90             objs[offset++] = codec.createBindingProxy(node);
91         }
92         verify(offset == objs.length);
93         return List.of(objs);
94     }
95
96     @Override
97     public int size() {
98         return objects.length;
99     }
100
101     @Override
102     public E get(final int index) {
103         final Object obj = OBJ_AA.getAcquire(objects, index);
104         // Check whether the object has been converted. The object is always non-null, but it can either be in DOM form
105         // (either a MapEntryNode or UnkeyedListEntryNode) or in Binding form. We know the exact class for the latter,
106         // as we are creating it via codec -- hence we can perform a direct comparison.
107         //
108         // We could do a Class.isInstance() check here, but since the implementation is not marked as final (yet) we
109         // would be at the mercy of CHA being able to prove this invariant.
110         return obj.getClass() == codec.generatedClass() ? (E) obj : load(index, (DataContainerNode) obj);
111     }
112
113     private @NonNull E load(final int index, final DataContainerNode node) {
114         final E ret = codec.createBindingProxy(node);
115         final Object witness;
116         return (witness = OBJ_AA.compareAndExchangeRelease(objects, index, node, ret)) == node ? ret : (E) witness;
117     }
118
119     @Override
120     @SuppressWarnings("checkstyle:parameterName")
121     public boolean remove(final Object o) {
122         throw uoe();
123     }
124
125     @Override
126     @SuppressWarnings("checkstyle:parameterName")
127     public boolean addAll(final Collection<? extends E> c) {
128         throw uoe();
129     }
130
131     @Override
132     @SuppressWarnings("checkstyle:parameterName")
133     public boolean addAll(final int index, final Collection<? extends E> c) {
134         throw uoe();
135     }
136
137     @Override
138     @SuppressWarnings("checkstyle:parameterName")
139     public boolean removeAll(final Collection<?> c) {
140         throw uoe();
141     }
142
143     @Override
144     @SuppressWarnings("checkstyle:parameterName")
145     public boolean retainAll(final Collection<?> c) {
146         throw uoe();
147     }
148
149     @Override
150     @SuppressWarnings("checkstyle:parameterName")
151     public void sort(final Comparator<? super E> c) {
152         throw uoe();
153     }
154
155     @Override
156     public void replaceAll(final UnaryOperator<E> operator) {
157         throw uoe();
158     }
159
160     @Override
161     protected void removeRange(final int fromIndex, final int toIndex) {
162         throw uoe();
163     }
164
165     private static UnsupportedOperationException uoe() {
166         return new UnsupportedOperationException("Modification not supported");
167     }
168 }