Teach BindingNormalizedNodeCache to cache leaf type objects
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / CodecDataObjectCustomizer.java
1 /*
2  * Copyright (c) 2019 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 com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13
14 import com.google.common.base.MoreObjects.ToStringHelper;
15 import com.google.common.base.Supplier;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import java.lang.reflect.Method;
19 import java.lang.reflect.Modifier;
20 import java.util.List;
21 import java.util.Map.Entry;
22 import java.util.Optional;
23 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
24 import javassist.CannotCompileException;
25 import javassist.CtClass;
26 import javassist.CtField;
27 import javassist.CtMethod;
28 import javassist.NotFoundException;
29 import org.eclipse.jdt.annotation.NonNull;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
32 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
33 import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
34 import org.opendaylight.yangtools.yang.binding.DataObject;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 /**
39  * Private support for generating AbstractDataObject specializations.
40  */
41 final class CodecDataObjectCustomizer implements Customizer {
42     private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
43     private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
44     private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
45     private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
46     private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
47     private static final CtClass CT_IIC = StaticClassPool.findClass(IdentifiableItemCodec.class);
48     private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
49     private static final CtClass CT_NCS = StaticClassPool.findClass(NodeContextSupplier.class);
50     private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
51     private static final CtClass[] EMPTY_ARGS = new CtClass[0];
52     private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
53     private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
54
55     private final ImmutableMap<Method, NodeContextSupplier> properties;
56     private final Entry<Method, IdentifiableItemCodec> keyMethod;
57
58     CodecDataObjectCustomizer(final ImmutableMap<Method, NodeContextSupplier> properties,
59             final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
60         this.properties = requireNonNull(properties);
61         this.keyMethod = keyMethod;
62     }
63
64     @Override
65     public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
66             throws NotFoundException, CannotCompileException {
67         // Generate members for all methods ...
68         LOG.trace("Generating class {}", generated.getName());
69         generated.addInterface(bindingClass);
70
71         for (Method method : properties.keySet()) {
72             generateMethod(loader, generated, method, CT_NCS, "resolve");
73         }
74         if (keyMethod != null) {
75             generateMethod(loader, generated, keyMethod.getKey(), CT_IIC, "resolveKey");
76         }
77
78         // Final bits: codecHashCode() ...
79         final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
80         codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
81         codecHashCode.setBody(codecHashCodeBody());
82         generated.addMethod(codecHashCode);
83
84         // ... equals
85         final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
86         codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
87         codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
88         generated.addMethod(codecEquals);
89
90         // ... and codecFillToString()
91         final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
92         codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
93         codecFillToString.setBody(codecFillToStringBody());
94         generated.addMethod(codecFillToString);
95
96         generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
97         return ImmutableList.of();
98     }
99
100     @Override
101     public Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
102         final CodecDataObjectCustomizer prev = CodecDataObjectBridge.setup(this);
103         try {
104             final Class<?> result = loader.get();
105
106             /*
107              * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
108              * we have the information needed to find them -- that information is being held in this instance and we
109              * leak it to a thread-local variable held by CodecDataObjectBridge.
110              *
111              * By default the JVM will defer class initialization to first use, which unfortunately is too late for
112              * us, and hence we need to force class to initialize.
113              */
114             try {
115                 Class.forName(result.getName(), true, result.getClassLoader());
116             } catch (ClassNotFoundException e) {
117                 throw new LinkageError("Failed to find newly-defined " + result, e);
118             }
119
120             return result;
121         } finally {
122             CodecDataObjectBridge.tearDown(prev);
123         }
124     }
125
126
127     @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
128         final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
129                 .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
130         verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
131         return verifyNotNull(found.get().getValue());
132     }
133
134     @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
135         return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
136             .getValue());
137     }
138
139     private String codecHashCodeBody() {
140         final StringBuilder sb = new StringBuilder()
141                 .append("{\n")
142                 .append("final int prime = 31;\n")
143                 .append("int result = 1;\n");
144
145         for (Method method : properties.keySet()) {
146             sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
147             .append(method.getName()).append("());\n");
148         }
149
150         return sb.append("return result;\n")
151                 .append('}').toString();
152     }
153
154     private String codecEqualsBody(final String ifaceName) {
155         final StringBuilder sb = new StringBuilder()
156                 .append("{\n")
157                 .append("final ").append(ifaceName).append(" other = $1;")
158                 .append("return true");
159
160         for (Method method : properties.keySet()) {
161             final String methodName = method.getName();
162             sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
163             .append("(), other.").append(methodName).append("())");
164         }
165
166         return sb.append(";\n")
167                 .append('}').toString();
168     }
169
170     private String codecFillToStringBody() {
171         final StringBuilder sb = new StringBuilder()
172                 .append("{\n")
173                 .append("return $1");
174         for (Method method : properties.keySet()) {
175             final String methodName = method.getName();
176             sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
177         }
178
179         return sb.append(";\n")
180                 .append('}').toString();
181     }
182
183     private static void generateMethod(final CodecClassLoader loader, final CtClass generated, final Method method,
184             final CtClass contextType, final String resolveMethod) throws CannotCompileException, NotFoundException {
185         LOG.trace("Generating for method {}", method);
186         final String methodName = method.getName();
187         final String methodArfu = methodName + "$$$ARFU";
188         final String methodNcs = methodName + "$$$NCS";
189
190         // NodeContextSupplier ...
191         final CtField ncsField = new CtField(contextType, methodNcs, generated);
192         ncsField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
193         generated.addField(ncsField, new StringBuilder().append(CodecDataObjectBridge.class.getName())
194             .append('.').append(resolveMethod).append("(\"").append(methodName).append("\")").toString());
195
196         // ... AtomicReferenceFieldUpdater ...
197         final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
198         arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
199         generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
200             .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
201             .append("\")").toString());
202
203         // ... corresponding volatile field ...
204         final CtField field = new CtField(CT_OBJECT, methodName, generated);
205         field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
206         generated.addField(field);
207
208         // ... and the getter
209         final Class<?> retType = method.getReturnType();
210         final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
211         final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
212
213         getter.setBody(new StringBuilder()
214             .append("{\n")
215             .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", ")
216                 .append(methodNcs).append(");\n")
217             .append('}').toString());
218         getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
219         generated.addMethod(getter);
220     }
221
222     private static String utilClass(final Method method) {
223         // We can either have objects or byte[], we cannot have Object[]
224         return method.getReturnType().isArray() ? "Arrays" : "Objects";
225     }
226 }